/////////////////////////////////////////////////////////////////
// TextView
//
// The Simple text editor ripped out of TapirMail
// The Flying Pig!
// Started 17/7/2003
//////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////
// Includes

#include "oslib/osmodule.h"
#include "oslib/messagetrans.h"
#include "oslib/wimp.h"
#include "oslib/dragasprite.h"
#include "oslib/macros.h"
#include "oslib/osfile.h"
#include "oslib/osfind.h"
#include "oslib/osgbpb.h"
#include "oslib/osbyte.h"
#include "oslib/colourtrans.h"
#include "oslib/wimpspriteop.h"
#include "oslib/osword.h"

#include "flexlib/flex.h"

#include "Comp.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <stddef.h>


//#if defined _DEBUG
#include <clib/kernel.h>
//#endif

//////////////////////////////////////////////////////////////////
// Defines

#define WORDALIGN(b)         (((b) + 3) & ~3)
#define RANGE(var,min,max)   if ((var) < (min)) var = min; \
                             if ((var) > (max)) var = max;
#define EDIT_MAX             (30)
#define SENDER_MAX           (64)
#define SUBJECT_MAX          (128)
#define TO_MAX               (1024)
#define CC_MAX               (1024)
#define BCC_MAX              (1024)
#define MILLIPOINT           (400)
#define EDIT_INVALID         (-1)
#define EMAIL_TEXT_X_OFFSET  (4)
#define EMAIL_TEXT_Y_OFFSET  (128)
#define EDIT_TEXT_Y_OFFSET_MAX (72)
#define EDIT_TEXT_Y_OFFSET_MIN (0)
#define EDIT_HEAD_EXTENT_MAX (72)
#define EDIT_HEAD_EXTENT_MIN (0)
#define EMAIL_CHAR_WIDTH     (77)
#define EMAIL_CHAR_XSIZE     (18)
#define INDENT_CYCLE         (4)
#define STYLE_BOLD           (1 << 0)
#define STYLE_ITALIC         (1 << 1)
#define STYLE_UNDERLINE      (1 << 2)
#define STYLE_STRIKE         (1 << 3)
#define STYLE_LINK           (1 << 4)
#define STYLE_HIDDEN         (1 << 5)
#define EDIT_CHARS_MAX       (1024)
#define EDIT_LINE_MAX        (1024)
#define DEFAULT_X_SCROLL     (32)
#define DEFAULT_Y_SCROLL     (32)

#define EDIT_CHARS_BLOCK     (1024)
#define EDIT_LINE_BLOCK      (1024)

#define EDIT_WIN_LINE_MIN    (30)
#define EMAIL_TEXT_X_SCROLL  (16)
#define RAMTRANSMIT_SIZE     (10 * 1024)
#define NULL_POLL_FAST       (1)
#define NULL_POLL_SLOW       (10)
#define NULL_POLL_SLOWEST    (100)
#define NULL_POLL_NETWORK    (0)
#define SELECT_MEM_MIN       (1024)

#define OPENWIN_OFFSET       (-60)
#define OPENWIN_OFFSET_MAX   (300)
#define MENU_XPOS_OFFSET     (-64)
#define ICONBAR_MENU_YPOS    (96 + 44 * 2 + 24 * 0)
#define FONT_LIST_MAX        (wimp_SIZEOF_MENU(1024))
#define FONT_LIST_IND_MAX    (1024 * 8)
#define FONT_NAME_LENGTH     (256)

#define PAGE_SCROLL_Y_ADD    (-2)
#define SIG_LINE_NUM         (4)
#define SIG_LINE_LENGTH      (80)
#define EDIT_TITLE_LEN       (16)
#define SMILEY_MAX           (32)
#define SMILEY_NAME_LEN      (12)

#define OS_BG_COLOUR         (os_COLOUR_VERY_LIGHT_GREY)
#define WIMP_BG_COLOUR       (wimp_COLOUR_VERY_LIGHT_GREY)
#define CARET_WIDTH          (2)
#define CARET_HEIGHT         (32)
#define CARET_COLOUR         (os_COLOUR_VERY_LIGHT_GREY ^ os_COLOUR_DARK_GREEN)
#define SEND_SMALL           (LINK_SEND_MAX)
#define SEND_LARGE           (1024*20)


//////////////////////////////////////////////////////////////////
// Structures

typedef enum
{
  PANE_INVALID = -1,

  PANE_NONE,
  PANE_TOP,
  PANE_BASE,

  PANE_NUM
} PANE;

typedef struct _Edit
{
  int                         nSize;
  wimp_w                      whWindow;
  wimp_w                      whHead;
  char                        *szRawEmail;
  char                        *szFormEmail;
  int                         *anLine;
  int                         nLineMax;
  int                         nFormattedTo;
  bool                        boInsert;
  bool                        boLineChange;
  bool                        boButtons;
  int                         nTextOffset;
  bool                        boChanged;
  char                        szWinTitle[EDIT_TITLE_LEN];

  int                         nEmailMem;
  int                         nLineMem;
} Edit;

typedef struct _Selection
{
  wimp_w                      whWindow;
  int                         nEmailNum;
  int                         nLineStart;
  int                         nCharStart;
  int                         nLineEnd;
  int                         nCharEnd;

} Selection;

typedef struct _Caret
{
  wimp_w                      whWindow;
  int                         nEditNum;
  int                         nLine;
  int                         nChar;
  int                         nPos;
  int                         nXPos;
  int                         nYPos;
  int                         nCharPref;

} Caret;

typedef struct _Smiley
{
  char                        szName[SMILEY_NAME_LEN];
  int                         nNameLength;
  int                         nOffset;
} Smiley;

typedef enum _SAVETYPE
{
  SAVETYPE_INVALID = -1,

  SAVETYPE_FILE,
  SAVETYPE_EMAILS,
  SAVETYPE_ICONTEXT,
  SAVETYPE_SELECTCHOOSE,
  SAVETYPE_COLUMN,
  SAVETYPE_SELECTSAVE,
  SAVETYPE_CLIPBOARD,

  SAVETYPE_NUM
} SAVETYPE;

typedef enum _LOADTYPE
{
  LOADTYPE_INVALID = -1,

  LOADTYPE_EDITTEXT,

  LOADTYPE_NUM
} LOADTYPE;

typedef enum
{
  RELPOS_INVALID = -1,

  RELPOS_BEFORE,
  RELPOS_WITHINSTART,
  RELPOS_WITHINEND,
  RELPOS_AFTER,
  RELPOS_OTHEREMAIL,

  RELPOS_NUM
} RELPOS;

typedef enum _NULLPOLL
{
  NULLPOLL_INVALID = -1,

  NULLPOLL_ACTIVE_FAST,
  NULLPOLL_ACTIVE_SLOW,
  NULLPOLL_DEACTIVE_FAST,
  NULLPOLL_DEACTIVE_SLOW,

  NULLPOLL_ACTIVE_NETWORK,
  NULLPOLL_DEACTIVE_NETWORK,

  NULLPOLL_RESET,

  NULLPOLL_NUM
} NULLPOLL;

typedef enum _QUERYACTION
{
  QUERYACTION_INVALID = -1,
  QUERYACTION_NONE,

  QUERYACTION_QUIT,
  QUERYACTION_CLOSEEDIT,

  QUERYACTION_NUM
} QUERYACTION;

typedef enum _DELETETYPE
{
  DELETETYPE_INVALID = -1,

  DELETETYPE_GUESS,
  DELETETYPE_OS4,
  DELETETYPE_OS5,

  DELETETYPE_NUM
} DELETETYPE;

//////////////////////////////////////////////////////////////////
// Global variables

static int                    gnWimpVersion;
static wimp_poll_flags        guPollFlags;
static char                   gpcTemp[256];
static char                   *gpcMessages;
static wimp_w                 gwhMain;
static wimp_w                 gwhSave;
static wimp_w                 gwhWarn;
static wimp_w                 gwhEdit;
static wimp_w                 gwhEdHe;
static wimp_w                 gwhQery;
static wimp_w                 gwhConf;

//static wimp_i                 gihIconBarIcon;
static int                    gnMenuWins = 0;
static wimp_w                 gawhMenuWinHandle[10];
static int                    gnMenuXpos = 0;
static int                    gnMenuYpos = 0;
static wimp_menu              *gpcMenuCurrent = NULL;
static wimp_menu              *gpcIconBarMenu;
static wimp_menu              *gpcNewMenu;
static wimp_menu              *gpcNewEditMenu;
static wimp_menu              *gpcLocalFontMenu;
static wimp_menu              *gpcRemoteFontMenu;
char                          *gpcFontMenuBuffer;
static wimp_t                 gnTaskHandle;
static bool                   gboDrag = FALSE;
static char                   gacFontRef[256];
static int                    gnMessageMyRef = 0;
static SAVETYPE               geSaveType = SAVETYPE_INVALID;
static LOADTYPE               geLoadType = LOADTYPE_INVALID;
static int                    gnSaveFileType;
static char                   gszSaveString[255];
static font_f                 ghFont;
static font_f                 ghFontB;
static font_f                 ghEmailFont;
static font_f                 ghEmailBFont;
static font_f                 ghEmailIFont;
static font_f                 ghEmailBIFont;

static int                    gnEditNum;
static Edit                   gasEdit[EDIT_MAX];

static int                    gnRowHeight;
static int                    gnTextYOffset;
static int                    gnEmailRowHeight;
static int                    gnEmailTextYOffset;
static int                    gnEmailTextWidth;
static int                    gnEllipsisWidth;
static int                    gnEllipsisWidthB;
static wimp_w                 gawhEditOpen[EDIT_MAX + 1];
static int                    ganEditOpen[EDIT_MAX + 1];

static int                    ganEmailSave[EDIT_MAX];
static int                    gnEmailSaveNum;
static int                    gnEmailSavePos;

static os_colour              gauIndCol[] = {os_COLOUR_LIGHT_BLUE,
                                             os_COLOUR_DARK_BLUE,
                                             os_COLOUR_LIGHT_RED,
                                             os_COLOUR_DARK_GREEN};
static Selection              gsSelect;
static int                    gnSelectLineStart;
static int                    gnSelectCharStart;
static int                    gnEmailSelected;
static int                    gnEditSaveSelected;
static Caret                  gsCaret;
static char                   *gpcCopySelect;
static int                    gnCopySelectSize;
static bool                   gboCaretLost;
static int                    gnMyRef;
static bool                   gboDelete;
static bool                   gboClipClaimed;
static char                   *gpcClipboard;
static int                    gnClipboardSize;
static wimp_message_data_xfer gsMessageStore;
static wimp_t                 gthSender;
static char                   gpcRamTransBuffer[RAMTRANSMIT_SIZE];
static int                    gnRamTransRef;
static char *                 gpcRamTransPos;
static int                    gnRamTransEmailPos;
static int                    gnRamTransAttcPos;
static int                    gnRamTransLeft;
static int                    gnFastPollAcc;
static int                    gnSlowPollAcc;
static bool                   gboNetworkPoll;
static bool                   gboPointerEdit;
static int                    gnPointerEmail;
static bool                   gboLinkPointer;
static bool                   gboColumnPointer;
static os_t                   gnMonotonicTime;
static os_t                   gnTimeInc;
static osspriteop_area        *gpcSprites;
static osspriteop_area        *gpcEmoticons;



static int                   gnBufferStart = 0;

static char                  *gpcCurrentEmail;
static char                  gszProgTitle[128];
static int                   gnTotalMailSize;
static int                   gnTotalMailSizeTemp;
static os_t                  gnTimeoutTime;
static int                   gnOpenOffset;
static int                   gnSelected;
static bool                  gboTentativeSelection;
static bool                  gboAccountsLoaded;
static char                  gszUnlockPassword[1024];
static bool                  gboLog;
static QUERYACTION           geQueryAction;
static char                  gszFont[FONT_NAME_LENGTH] = "Trinity.Medium";
static char                  gszFontB[FONT_NAME_LENGTH] = "Trinity.Bold";
//static char                  gszLocalChoiceFont[FONT_NAME_LENGTH] = "";
//static char                  gszRemoteChoiceFont[FONT_NAME_LENGTH] = "";
static int                   gnAddressNum;
static int                   gnAttachmentNum;
static int                   gnEmailCharWidth = EMAIL_CHAR_XSIZE;
static wimp_w                gwhWindowClose;
static DELETETYPE            geDeleteTypeConfig;
static bool                  gboIyonixDelete;
static osspriteop_trans_tab  *gpsTransTable = NULL;
static int                   gnSmileyNum;
static Smiley                gasSmileys[SMILEY_MAX];
static bool                  gboEmoticons;
static bool                  gboUIDDesync;

static int                   gnLinkHandle = -1;
static wimp_t                ghLinkTask = NULL;
static char                  gpcLinkBuffer[LINK_RAM_BUFFER];
static int                   gnLinkTransRef = -1;
static char                  *gpcLinkTransPos = NULL;
static int                   gnLinkTransLeft = 0;

static bool                  gboConcatonate;
static bool                  gboSpecialChars;
static bool                  gboAllowMouseCaret;

static char                  gszMiniBuffer[8];
static int                   gnMiniBufferSize;
static int                   gboBuffersLinked;

static bool                  gboWarnOnClose;
static int                   gnDataSend;
static char                  *gpcDataSend;
static int                   ganDataSize;
static int                   gnSendSize;

//////////////////////////////////////////////////////////////////
// Main application

void RedrawWindow (wimp_block *pcBlock);
void InsertEmailColour (char *szFormatted, int * pnMemory, os_colour uCol, os_colour uBack);
int CopyFormatted (char * szFrom, char * szTo, int nLen, os_colour uCol, os_colour uBack);
void CheckStyles (char * szWordStart, char * szWordEnd, unsigned int * puStyles);
void SelectReOrder (Selection *psSelection);
void InvertSelection (Selection * psSelect, wimp_draw *psRedraw);
void ChangeSelection (Selection * psNew);
void DragSelectionBox (int nEmailNum, int nXPos, int nYPos);
void NullPoll (wimp_block *pcBlock);
RELPOS PointRelativeSelection (int nEmailNum, int nXPos, int nYPos, Selection * psSelect, int * pnLine, int * pnChar);
int CreateEditWindow (void);
int CreateEditWindowAddress (char * szAddress);
void RedrawEdit (wimp_draw *psRedraw, int nEditNum);
int FormatLines (int nEditNum, int nLineStart, int nLineEnd);
void ReFormat (int nEditNum, int nChangedLine, int nLineEnd, int * psStartLine, int * psEndLine);
void FindEditCoords (int nEditNum, int nXPos, int nYPos, int * pnLine, int * pnChar);
void SetCaretPos (int nEditNum, int nLine, int nChar);
void InsertChar (int nEditNum, int nPos, int nLine, char cChar);
void UpdateEditWindow (int nEditNum, int nStartLine, int nEndLine);
void MoveCaretRight (int nRight);
void MoveCaretLeft (int nLeft);
void MoveCaretDown (int nDown);
void MoveCaretUp (int nUp);
void DeleteChar (int nEditNum, int nPos, int nLine);
void DragCentred (int nXPos, int nYPos, char * szIconName);
bool DeleteSection (int nEditNum, Selection * psSelect);
void ShiftSelection (Caret * psCaretPre, Caret * psCaret);
void SearchCaretRight (char * szSearch);
void SearchCaretLeft (char * szSearch);
void InsertString (int nEditNum, int nPos, int nLine, char * szString);
void ShiftLines (int nEditNum, int nLineStart, int nLine, int nChar);
void LoseCaret (wimp_block *pcBlock);
void ScrollRequest (wimp_block *pcBlock);
void CreateEditSelect (int nEditNum, char ** ppcMem, int * pnSize);
int CountNonControl (char * szString, int nLength);
void ClearCaret (void);
void InsertText (int nEditNum, int nPos, int nLine, char * szString, int nLength);
void InsertTextFlex (int nEditNum, int nPos, int nLine, char * * pszString, int nLength);
void ClearSelection (void);
void SetEditHeight (int nEditNum, int nLine);
void CopySelectToClip (void);
void ClearClipboard (void);
void DataRequest (wimp_block * pcBlock);
void ClaimEntity (wimp_block * pcBlock);
void LaunchURL (wimp_block * pcBlock);
void PasteClipboard (void);
void ScrollWindowToCaret (void);
void RamFetchReturned (wimp_block *pcBlock);
void SetNullPollActive (bool boActive);
void PointerEntering (wimp_block * pcBlock);
void PointerLeaving (wimp_block * pcBlock);
void FindWordAtCoords (int nEmailNum, int nXPos, int nYPos, char ** pszWordStart, char ** pszWordEnd);
bool WordIsLink (char * szWordStart, char * szWordEnd);
int WordIsSmiley (char * szWordStart, char * szWordEnd);
void ClickOnURL (int nEmailNum, int nXPos, int nYPos);
void PlotSmileys(font_f hFont, char *szPlotLine, int nXPos, int nYPos, int nPlotLen);
void OpenWindowInitPane (wimp_w whWindow, wimp_w whPane, PANE ePaneType);
void OpenWindowInitPaneCentreSize (wimp_w whWindow, wimp_w whPane, PANE ePaneType, int nWidth, int nHeight);
void OpenWindowInitPaneNew (wimp_w whWindow, wimp_w whPane, PANE ePaneType);
void OpenWindowInitNew (wimp_w whWindow);
void DestroyEditWindow (int nEditNum);
char * TagArg (char * szTag, char * szArg0);
void ShowWarningTagArg (char * szTag, char * szArg0);
void SetIconSprite (char * szSprite, wimp_w whWindow, wimp_i ihIcon);
void ShowQueryTag (char * szTag, char * szContinueTag, char * szCancelTag, QUERYACTION eAction);
void ShowQueryTagVolatile (char * szTag, char * szContinueTag, char * szCancelTag, QUERYACTION eAction);
void CloseQuery (void);
void SetupIconbarMenu (void);
int cmpstr (char * szString1, char * szString2);
void SetEmailSave (int nEmailNum);
int EmailSaveSize (void);
void SetSubMenu (wimp_menu * pcMain, int nItem, wimp_menu * pcSub);
void SetMenuItemGreyness (bool boState, wimp_menu * pcMenu, int nItem);
void SetMenuItemTicked (bool boTicked, wimp_menu * pcMenu, int nItem);;
void SetUpNewMenu (void);
//int LoadFileFlex (char * szFilename, char * * ppcMemory, int * pnSize);
void QueryContinue (void);
void QueryCancel (void);
void PreQuit (void);
int ExtendLineMem (int nEditNum);
int ExtendEmailMem (int nEditNum);
int ExtendLineMemTo (int nEditNum, int nSize);
int ExtendEmailMemTo (int nEditNum, int nSize);
void FindAdditionalFonts (void);
void ReleaseAdditionalFonts (void);
void ModeChange (void);
//void OpenChoicesLocalFontMenu (void);
//void ChoicesLocalFontSelect (wimp_selection * psSelection);
//void OpenChoicesRemoteFontMenu (void);
//void ChoicesRemoteFontSelect (wimp_selection * psSelection);
void SetUpFontMenu (wimp_menu * pcMenu, char * szFont);
bool ChangeListFonts (char * szFont, char * szFontB);
void ShowButtons (int nEditNum, bool boShow);
void CloseWindowHandlePane (wimp_w whWindow, wimp_w whPane);
inline void EditChanged (int nEditNum);
void EditChangedInitial (int nEditNum);
void SetIconTextIncrement (char * szText, wimp_w whWindow, wimp_i ihIcon);
void IconTextIncrement (wimp_w whWindow, wimp_i ihIcon);
void SetDeleteType (DELETETYPE eDeleteType);
char * strnchr (char * szString, char cChar, int nLength);
char * strnzchr (char * szString, char cChar, int nLength);
void GenerateTransTable (void);
void PopulateSmileys (void);
void RedrawAllEmailEditWindows (void);
int ConvertString (char * szOutput, char * szInput, int nBufferLen);
void ShowWarning (os_error * sError);
void MenuWarning (wimp_block *pcBlock);
void EditAttachmentsIconUpdate (int nEditNum);

void DrawCaret (int nXPos, int nYPos);
void RedrawCaret (void);
void ConfigSetUpWindow (void);
void ConfigSetFromWindow (void);

void InsertSpecialAtCaret (char * pcData, int nSize);
void InsertSpecialKey (int nKey, int * pnStartLine, int * pnEndLine);
void SendEditData (int nEditNum);

//////////////////////////////////////////////////////////////////
// Respond to swi errors
inline void err (os_error * psError)
{
  if (psError)
  {
    ShowWarning (psError);
  }
}

//////////////////////////////////////////////////////////////////
// Log a string to reporter
inline void Log (char * szPrefix, char * szText)
{
  char                        szLog[1024];
  _kernel_swi_regs            sRegs;

  if (gboLog)
  {
    strncpy (szLog, szPrefix, 512);
    strncat (szLog, szText, 512);

    sRegs.r[0] = (int)szLog;
    _kernel_swi (Report_Text0, & sRegs, & sRegs);
  }
}

//////////////////////////////////////////////////////////////////
// Log a string to reporter
inline void LogVar (char * szFormat, int nVariable)
{
  char                        szLog[1024];
  _kernel_swi_regs            sRegs;

  if (gboLog)
  {
    sprintf (szLog, szFormat, nVariable);
    sRegs.r[0] = (int)szLog;
    _kernel_swi (Report_Text0, & sRegs, & sRegs);
  }
}

//////////////////////////////////////////////////////////////////
// Main program
int main (int argc, char * * argv)
{
  wimp_block                  cBlock;
  int                         nCount;
  wimp_event_no               nEvent;
  wimp_MESSAGE_LIST(19) sMessages = {{message_DATA_SAVE,
                                      message_DATA_SAVE_ACK,
                                      message_DATA_LOAD,
//                                      message_DATA_OPEN,
                                      message_RAM_FETCH,
                                      message_RAM_TRANSMIT,
                                      message_CLAIM_ENTITY,
                                      message_DATA_REQUEST,
                                      message_MODE_CHANGE,
                                      message_HELP,
                                      message_MENU_WARNING,
                                      message_URL_LAUNCH,
                                      message_LINK_CONTROL,
                                      message_LINK_OPEN,
                                      message_LINK_CLOSE,
                                      message_LINK_SEND,
                                      message_LINK_DATASAVE,
                                      message_LINK_RAMFETCH,
                                      message_LINK_RAMTRANSMIT,
                                      0u}};
  wimp_message_list *psUserMessages = (wimp_message_list*)&sMessages;
  os_t                        nMonotonicTime;
  int                         nAllocSuccess;
  bool                        boEmailLaunch;
  char                        szEmailLaunchTo[TO_MAX];
  int                         nArgPos;

  // Check for parameters
  boEmailLaunch = FALSE;
  szEmailLaunchTo[0] = 0;
  nArgPos = 1;
  while (nArgPos < argc)
  {
    REPORT (argv[nArgPos]);

    if (strcmp (argv[nArgPos], "-url") == 0)
    {
      nArgPos++;
      if (nArgPos < argc)
      {
        if (strncmp (argv[nArgPos], "mailto:", 7) == 0)
        {
          strncpy (szEmailLaunchTo, argv[nArgPos] + 7, TO_MAX);
        }
        else
        {
          strncpy (szEmailLaunchTo, argv[nArgPos], TO_MAX);
        }
        boEmailLaunch = TRUE;
      }
    }
    nArgPos++;
  }

  // Variables
  guPollFlags = FLAGS;
  gnFastPollAcc = 0;
  gnSlowPollAcc = 0;
  gboNetworkPoll = FALSE;
  gnTimeoutTime = -1;
  gpcCurrentEmail = NULL;
  gnOpenOffset = 0;
  gnSelected = 0;
  gboTentativeSelection = TRUE;
  strcpy (gszUnlockPassword, "");
  geQueryAction = QUERYACTION_NONE;
  gnAddressNum = 0;
  gnAttachmentNum = 0;
  SetDeleteType (DELETETYPE_GUESS);
  gboUIDDesync = FALSE;

  SetNullPollActive (NULLPOLL_ACTIVE_SLOW);

  gboPointerEdit = FALSE;
  gnPointerEmail = EDIT_INVALID;
  gboLinkPointer = FALSE;
  gboColumnPointer = FALSE;
  gboLog = FALSE;

  FindAdditionalFonts ();

  // Set up blank edit structures
  for (nCount = 0; nCount < EDIT_MAX; nCount++)
  {
    gasEdit[nCount].nSize = -1;
    gasEdit[nCount].whWindow = wimp_BACKGROUND;
    gasEdit[nCount].whHead = wimp_BACKGROUND;
    gasEdit[nCount].szRawEmail = NULL;
    gasEdit[nCount].szFormEmail = NULL;
    gasEdit[nCount].boInsert = FALSE;
    gasEdit[nCount].boLineChange = FALSE;
    gasEdit[nCount].boButtons = FALSE;
    gasEdit[nCount].nTextOffset = EDIT_TEXT_Y_OFFSET_MIN;
    gasEdit[nCount].boChanged = FALSE;
    strcpy (gasEdit[nCount].szWinTitle, "");

    gawhEditOpen[nCount] = wimp_BACKGROUND;
    ganEditOpen[nCount] = EDIT_INVALID;
  }
  gawhEditOpen[EDIT_MAX] = wimp_BACKGROUND;
  ganEditOpen[EDIT_MAX] = EDIT_INVALID;

  gnEditNum = 0;
  gwhWindowClose = 0;

  // Set up some arbitrary details
  gnBufferStart = 0;
  gboWarnOnClose = FALSE;
  gnDataSend = -1;
  gpcDataSend = NULL;
  ganDataSize = 0;
  gnSendSize = SEND_LARGE;

  gnEmailSelected = EDIT_INVALID;
  gnEditSaveSelected = EDIT_INVALID;
  gnTotalMailSize = 0;
  gnTotalMailSizeTemp = 0;

  gboConcatonate = FALSE;
  gboSpecialChars = TRUE;
  gboAllowMouseCaret = TRUE;

  gsSelect.nLineStart = 20;
  gsSelect.nCharStart = 15;
  gsSelect.nLineEnd = 29;
  gsSelect.nCharEnd = 11;
  gsSelect.whWindow = wimp_BACKGROUND;
  gsSelect.nEmailNum = EDIT_INVALID;

  gpcCopySelect = NULL;
  gnCopySelectSize = 0;
  gboClipClaimed = FALSE;
  gpcClipboard = NULL;
  gnClipboardSize = 0;
  gboEmoticons = TRUE;

  gnRamTransRef = -1;
  gpcRamTransPos = NULL;
  gnRamTransLeft = 0;
  gnRamTransEmailPos = 0;
  gnRamTransAttcPos = 0;

  ClearCaret ();
  gboCaretLost = TRUE;
  gnMyRef = -1;

  // Set up general variables
  gnLinkTransRef = -1;
  gpcLinkTransPos = NULL;
  gnLinkTransLeft = 0;

  // Load Messages file
  gpcMessages = osmodule_alloc (17 + sizeof(MESSAGES));
  strcpy(gpcMessages + 16, MESSAGES);

  messagetrans_open_file ((messagetrans_control_block*)gpcMessages,
    gpcMessages + 16, 0);

  // Initialise task
  gnTaskHandle = wimp_initialise (wimp_VERSION_RO3, Tag ("Tsk"),
    psUserMessages, & gnWimpVersion);

  // Initialise the flex memory
  strncpy (gszProgTitle, Tag("Tsk"), sizeof (gszProgTitle));
  flex_init (gszProgTitle, 0, 0);

  // Create a clipboard area
  nAllocSuccess = flex_alloc ((flex_ptr)(& gpcCopySelect), SELECT_MEM_MIN);
  if (nAllocSuccess == 0)
  {
    gpcCopySelect = NULL;
  }
  gnCopySelectSize = 0;

  // Load templates
  for (nCount = 0; nCount < 256; nCount++)
  {
    gacFontRef[nCount] = 0;
  }
  wimp_open_template (TEMPLATES);
  gwhSave = LoadTemplate ("Save");
  gwhWarn = LoadTemplate ("Warning");
  gwhEdit = LoadTemplate ("Edit");
  gwhQery = LoadTemplate ("Query");
  gwhConf = LoadTemplate ("Config");

  SetIconText (Tag ("SaveEmail"), gwhSave, 2);

  // Load sprites
  gpcSprites = LoadSprites (SPRITES);

  gwhEdHe = LoadTemplateSprites ("EditHead", gpcSprites);

  // Load emoticon sprites
  gpcEmoticons = LoadSprites (EMOTICONS);
  PopulateSmileys ();
  GenerateTransTable ();

  // Create menus
//  gpcIconBarMenu = CreateMenu (Tag ("Menu1"));
  gpcNewMenu = CreateMenu (Tag ("Menu4"));
  gpcNewEditMenu = CreateMenu (Tag ("Menu4-0"));

  SetSubMenu (gpcNewMenu, 1, (wimp_menu*)gwhSave);
  SetSubMenu (gpcNewMenu, 2, gpcNewEditMenu);

//  gpcIconBarMenu->entries[1].sub_menu = NULL;

  // Create space for the font menus
  gpcLocalFontMenu = (wimp_menu*)malloc (FONT_LIST_MAX);
  gpcRemoteFontMenu = (wimp_menu*)malloc (FONT_LIST_MAX);
  gpcFontMenuBuffer = malloc (FONT_LIST_IND_MAX);

  // Close templates
  wimp_close_template ();

  // Set up version info
  SetIconText (VERSION_STRING_LONG, gawhMenuWinHandle[0], 3);

  // Create icon
//  gihIconBarIcon = CreateIconbarIcon (Tag ("Icn"), 92, 68);

  // Open an email window if -url argument set
  if (boEmailLaunch)
  {
    CreateEditWindowAddress (szEmailLaunchTo);
    boEmailLaunch = FALSE;
  }

  xos_read_monotonic_time (& gnMonotonicTime);
  gnTimeInc = NULL_POLL_SLOWEST;

  // Main polling loop
  //////////////////////////////////////////////////////////////////
  do
  {
    nEvent = wimp_poll_idle (guPollFlags, & cBlock, gnMonotonicTime, NULL);
    xos_read_monotonic_time (& nMonotonicTime);
    if (nMonotonicTime > gnMonotonicTime)
    {
      gnMonotonicTime = nMonotonicTime + gnTimeInc;
    }
    switch (nEvent)
    {
      case  0: // Null
        NullPoll (& cBlock);
        break;
      case  1: // Redraw window
        RedrawWindow (& cBlock);
        break;
      case  2: // Open window
        OpenWindow (& cBlock);
        break;
      case  3: // Closewindow
        CloseWindow (& cBlock);
        break;
      case  4: // Pointer leaving window
        PointerLeaving (& cBlock);
        break;
      case  5: // Pointer entering window
        PointerEntering (& cBlock);
        break;
      case  6:
        MouseClick (& cBlock);
        break;
      case  7: // User drag box
        UserDragBox (& cBlock);
        break;
      case  8: // Keys
        Keys (& cBlock);
        break;
      case  9:
        MenuSelect (& cBlock);
        break;
      case 10: // Scroll request
        ScrollRequest (& cBlock);
        break;
      case 11: // Lose caret
        LoseCaret (& cBlock);
        break;
      case 12: // Gain caret
        break;
      case 17: // Receive message
      case 18:
        Receive (& cBlock);
        break;
      case 19: // UserMessage Acknowledge
        Acknowledged (& cBlock);
        break;
      default: // Default
        break;
    }
  } while TRUE;

  //////////////////////////////////////////////////////////////////

  return 0;
}

//////////////////////////////////////////////////////////////////
// Poll 16,17: Message received
void Receive (wimp_block *pcBlock)
{
  switch (pcBlock->message.action)
  {
    case message_QUIT:
      PreQuit ();
      break;
    case message_DATA_SAVE:
      DataSave (pcBlock);
      break;
    case message_DATA_SAVE_ACK:
      DataSaveAck (pcBlock);
      break;
    case message_DATA_LOAD:
      DataLoad (pcBlock);
      break;
//    case message_DATA_OPEN:
//      DataOpen (pcBlock);
//      break;
    case message_RAM_FETCH:
      RamFetch (pcBlock);
      break;
    case message_RAM_TRANSMIT:
      RamTransmit (pcBlock);
      break;
    case message_CLAIM_ENTITY:
      ClaimEntity (pcBlock);
      break;
    case message_DATA_REQUEST:
      DataRequest (pcBlock);
      break;
    case message_MODE_CHANGE:
      ModeChange ();
    case message_HELP:
      Help (pcBlock);
      break;
    case message_MENU_WARNING:
      MenuWarning (pcBlock);
      break;
    case message_URL_LAUNCH:
      LaunchURL (pcBlock);
      break;

    case message_LINK_CONTROL:
      ReceiveLinkControl (pcBlock);
      break;
    case message_LINK_OPEN:
      ReceiveLinkOpen (pcBlock);
      break;
    case message_LINK_CLOSE:
      ReceiveLinkClose (pcBlock);
      break;
    case message_LINK_SEND:
      ReceiveLinkSend (pcBlock);
      break;
    case message_LINK_DATASAVE:
      ReceiveLinkDataSave (pcBlock);
      break;
    case message_LINK_RAMFETCH:
      ReceiveLinkRamFetch (pcBlock);
      break;
    case message_LINK_RAMTRANSMIT:
      ReceiveLinkRamTransmit (pcBlock);
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Poll 19: Message acknowledge received
void Acknowledged (wimp_block *pcBlock)
{
  switch (pcBlock->message.action)
  {
    case message_RAM_FETCH:
      RamFetchReturned (pcBlock);
      break;
    case message_URL_LAUNCH:
      OpenURLReturned (pcBlock);
      break;
  }
  gnMessageMyRef = 0;
}

//////////////////////////////////////////////////////////////////
// Pre quit
void PreQuit (void)
{
  int                         nEditNum;
  bool                        boChanged;

  nEditNum = 0;
  boChanged = FALSE;
  while ((!boChanged) && (nEditNum < EDIT_MAX))
  {
    if ((gasEdit[nEditNum].whWindow != wimp_BACKGROUND)
      && (gasEdit[nEditNum].boChanged))
    {
      boChanged = TRUE;
    }
    nEditNum++;
  }
  if ((boChanged) && (gboWarnOnClose))
  {
    ShowQueryTag ("Ms7", "MsYes", "MsNo", QUERYACTION_QUIT);
  }
  else
  {
    Quit ();
  }
}

//////////////////////////////////////////////////////////////////
// Quit task
void Quit (void)
{
  int                         nCount;
  int                         nRemove;

  ReleaseAdditionalFonts ();

  // Free the fonts
  for (nCount = 0; nCount < 256; nCount++)
  {
    if (gacFontRef[nCount])
    {
      for (nRemove = 0; nRemove < gacFontRef[nCount]; nRemove++)
      {
        xfont_lose_font ((font_f)nCount);
      }
    }
  }

  // Close the program
  wimp_close_down (gnTaskHandle);
}

//////////////////////////////////////////////////////////////////
// The edit window has been changed
inline void EditChanged (int nEditNum)
{
  if (!gasEdit[nEditNum].boChanged)
  {
    EditChangedInitial (nEditNum);
  }
}

//////////////////////////////////////////////////////////////////
// The edit window has been changed for the first time
void EditChangedInitial (int nEditNum)
{
  wimp_window_info            sWindow;
  wimp_outline                sOutline;

  gasEdit[nEditNum].boChanged = TRUE;
  sWindow.w = gasEdit[nEditNum].whWindow;
  xwimp_get_window_info_header_only (& sWindow);
  strncpy (sWindow.title_data.indirected_text.text,
    Tag("EditTitle"), sWindow.title_data.indirected_text.size - 2);
  strcat (sWindow.title_data.indirected_text.text, " *");
  if (gnWimpVersion < 502)
  {
    // Non nested wimp
    sOutline.w = gasEdit[nEditNum].whWindow;
    xwimp_get_window_outline (& sOutline);
    xwimp_force_redraw (wimp_BACKGROUND, sOutline.outline.x0,
      sWindow.visible.y1, sOutline.outline.x1, sOutline.outline.y1);
  }
  else
  {
    // Nested wimp
    xwimp_force_redraw_title (gasEdit[nEditNum].whWindow);
  }
}

//////////////////////////////////////////////////////////////////
// Poll 6: Process mouse clicks
void MouseClick (wimp_block *pcBlock)
{
  int                         nXPos       = pcBlock->pointer.pos.x;
  int                         nYPos       = pcBlock->pointer.pos.y;
  wimp_mouse_state            nButton     = pcBlock->pointer.buttons;
  wimp_w                      whWindow    = pcBlock->pointer.w;
  wimp_i                      ihIcon      = pcBlock->pointer.i;
  char                        *szFileIcon;
  wimp_window_state           sState;
  int                         nWinXPos;
  int                         nWinYPos;
  Selection                   sSelect;
  bool                        boWinFound;
  int                         nEditSearch;
  RELPOS                      eRelPos;
  int                         nLine;
  int                         nChar;
  int                         nStartLine;
  int                         nEndLine;
  bool                        boClearSelect;
  char                        szSnip[1024];
  int                         nLength;
  bool                        boUpdate;

  boWinFound = FALSE;
  sState.w = whWindow;
  xwimp_get_window_state (& sState);
  nWinXPos = nXPos - sState.visible.x0 + sState.xscroll;
  nWinYPos = nYPos - sState.visible.y1 + sState.yscroll;

  if (whWindow == wimp_ICON_BAR)
  {
    switch (nButton)
    {
      case wimp_CLICK_SELECT:
        CreateEditWindow ();
        break;
      case wimp_CLICK_MENU:
        SetupIconbarMenu ();
        OpenMenu (gpcIconBarMenu, nXPos + MENU_XPOS_OFFSET,
          ICONBAR_MENU_YPOS);
        break;
      case wimp_CLICK_ADJUST:
        break;
    }
    boWinFound = TRUE;
  }

  if (whWindow == gwhSave)
  {
    switch (ihIcon)
    {
      case (wimp_i)3: // Save drag
        if (nButton & (wimp_DRAG_SELECT | wimp_DRAG_ADJUST))
        {
          szFileIcon = GetIconText (gwhSave, 3);
          gnSaveFileType = 0xfff;
          DragSprite (whWindow, ihIcon, "file_fff");
          geSaveType = SAVETYPE_FILE;
        }
        break;
      case (wimp_i)1: // Cancel
        CloseMenu ();
        break;
      case (wimp_i)0: // Save
        gnSaveFileType = 0xfff;
        SaveSave ();
        if (nButton != wimp_CLICK_ADJUST)
        {
          CloseMenu ();
        }
        break;
    }
    boWinFound = TRUE;
  }

  if (whWindow == gwhWarn)
  {
    switch (ihIcon)
    {
      case (wimp_i)0: // OK
        CloseWarning ();
        break;
    }
    boWinFound = TRUE;
  }

  if (whWindow == gwhQery)
  {
    switch (ihIcon)
    {
      case (wimp_i)0: // Continue
        QueryContinue ();
        break;
      case (wimp_i)5: // Cancel
        QueryCancel ();
        break;
    }
    boWinFound = TRUE;
  }

  if (whWindow == gawhMenuWinHandle[0])
  {
    switch (ihIcon)
    {
      case (wimp_i)8: // Email
        OpenURL (Tag("Email"));
        if (nButton != wimp_CLICK_ADJUST)
        {
          CloseMenu ();
        }
        break;
      case (wimp_i)9: // Website
        OpenURL (Tag("Website"));
        if (nButton != wimp_CLICK_ADJUST)
        {
          CloseMenu ();
        }
        break;
    }
    boWinFound = TRUE;
  }

  if (whWindow == gwhConf)
  {
    switch (ihIcon)
    {
      case (wimp_i)0: // Cancel
        ConfigSetUpWindow ();
        if (nButton & wimp_CLICK_SELECT)
        {
          CloseWindowHandle (gwhConf);
        }
        break;
      case (wimp_i)1: // OK
        ConfigSetFromWindow ();
        if (nButton & wimp_CLICK_SELECT)
        {
          CloseWindowHandle (gwhConf);
        }
        break;
    }
  }

  // Check for all of the edit windows
  nEditSearch = 0;
  while ((!boWinFound) && (gawhEditOpen[nEditSearch] != wimp_BACKGROUND))
  {
    if (whWindow == gawhEditOpen[nEditSearch])
    {
      eRelPos = PointRelativeSelection (ganEditOpen[nEditSearch],
        nXPos, nYPos, & gsSelect, & nLine, & nChar);

      if (nButton == wimp_CLICK_MENU)
      {
        gnEmailSelected = ganEditOpen[nEditSearch];
        SetUpNewMenu ();
        SetEmailSave (gnEmailSelected);
        OpenMenu (gpcNewMenu, nXPos + MENU_XPOS_OFFSET, nYPos);
      }

      if (nButton & wimp_SINGLE_SELECT)
      {
        if (gboAllowMouseCaret)
        {
          SetCaretPos (ganEditOpen[nEditSearch], nLine, nChar);
          gsCaret.nCharPref = gsCaret.nChar;
        }

        if ((eRelPos != RELPOS_WITHINSTART)
          && (eRelPos != RELPOS_WITHINEND))
        {
          ClearSelection ();
        }
      }

      if (nButton & wimp_DOUBLE_SELECT)
      {
        // Launch a URL
        ClickOnURL (ganEditOpen[nEditSearch], nXPos, nYPos);
      }

      if (nButton & wimp_SINGLE_ADJUST)
      {
        if (eRelPos == RELPOS_OTHEREMAIL)
        {
          sSelect.whWindow = whWindow;
          sSelect.nEmailNum = ganEditOpen[nEditSearch];
          sSelect.nLineStart = gsCaret.nLine;
          sSelect.nCharStart = gsCaret.nChar;
          sSelect.nLineEnd = nLine;
          sSelect.nCharEnd = nChar;
          ChangeSelection (& sSelect);
        }

        if (gboAllowMouseCaret)
        {
          SetCaretPos (ganEditOpen[nEditSearch], nLine, nChar);
          gsCaret.nCharPref = gsCaret.nChar;
        }

        if ((eRelPos == RELPOS_BEFORE)
          || (eRelPos == RELPOS_WITHINSTART))
        {
          sSelect = gsSelect;
          sSelect.nLineStart = nLine;
          sSelect.nCharStart = nChar;
          ChangeSelection (& sSelect);
        }
        if ((eRelPos == RELPOS_AFTER)
          || (eRelPos == RELPOS_WITHINEND))
        {
          sSelect = gsSelect;
          sSelect.nLineEnd = nLine;
          sSelect.nCharEnd = nChar;
          ChangeSelection (& sSelect);
        }
      }

      if (nButton & wimp_DRAG_SELECT)
      {
        if ((eRelPos == RELPOS_WITHINSTART)
          || (eRelPos == RELPOS_WITHINEND))
        {
          CreateEditSelect (ganEditOpen[nEditSearch],
            & gpcCopySelect, & gnCopySelectSize);
          geSaveType = SAVETYPE_SELECTSAVE;
          DragCentred (nXPos, nYPos, "file_fff");
        }
        else
        {
          if (gboAllowMouseCaret)
          {
            SetCaretPos (ganEditOpen[nEditSearch], nLine, nChar);
            gsCaret.nCharPref = gsCaret.nChar;
          }

          gnSelectLineStart = nLine;
          gnSelectCharStart = nChar;
          DragSelectionBox (ganEditOpen[nEditSearch], nXPos, nYPos);
          geSaveType = SAVETYPE_SELECTCHOOSE;
          SetNullPollActive (NULLPOLL_ACTIVE_FAST);
        }

      }

      if (nButton & wimp_DRAG_ADJUST)
      {
        if (gboAllowMouseCaret)
        {
          SetCaretPos (ganEditOpen[nEditSearch], nLine, nChar);
          gsCaret.nCharPref = gsCaret.nChar;
        }

        gnSelectLineStart = nLine;
        gnSelectCharStart = nChar;
        if ((eRelPos == RELPOS_BEFORE)
          || (eRelPos == RELPOS_WITHINSTART))
        {
          gnSelectLineStart = gsSelect.nLineEnd;
          gnSelectCharStart = gsSelect.nCharEnd;
        }
        if ((eRelPos == RELPOS_AFTER)
          || (eRelPos == RELPOS_WITHINEND))
        {
          gnSelectLineStart = gsSelect.nLineStart;
          gnSelectCharStart = gsSelect.nCharStart;
        }
        DragSelectionBox (ganEditOpen[nEditSearch], nXPos, nYPos);
        geSaveType = SAVETYPE_SELECTCHOOSE;
        SetNullPollActive (NULLPOLL_ACTIVE_FAST);
      }
      boWinFound = TRUE;
    }

    if ((!boWinFound)
      && (gasEdit[ganEditOpen[nEditSearch]].whHead == whWindow))
    {
      boClearSelect = FALSE;
      switch (ihIcon)
      {
        case 0: // Cut
          CopySelectToClip ();
          if (gboAllowMouseCaret)
          {
            if (nButton & wimp_CLICK_ADJUST)
            {
              boUpdate = DeleteSection (ganEditOpen[nEditSearch],
                & gsSelect);
              if (ganEditOpen[nEditSearch] == gsCaret.nEditNum)
              {
                if (gsCaret.nChar == 0)
                {
                  nLength = ConvertString(szSnip, Tag("Snip"),
                    sizeof(szSnip));
                }
                else
                {
                  nLength = ConvertString(szSnip, Tag("SnipMid"),
                    sizeof(szSnip));
                }
                InsertText (gsCaret.nEditNum, gsCaret.nPos, gsCaret.nLine,
                  szSnip, nLength);
                boUpdate = TRUE;
              }
              if (boUpdate)
              {
                EditChanged (ganEditOpen[nEditSearch]);
                ReFormat (ganEditOpen[nEditSearch], gsCaret.nLine, -1,
                  & nStartLine, & nEndLine);
                UpdateEditWindow (ganEditOpen[nEditSearch], nStartLine,
                  nEndLine);
              }
            }
            else
            {
              if (DeleteSection (ganEditOpen[nEditSearch], & gsSelect))
              {
                ReFormat (ganEditOpen[nEditSearch], gsCaret.nLine, -1,
                  & nStartLine, & nEndLine);
                UpdateEditWindow (ganEditOpen[nEditSearch], nStartLine,
                  nEndLine);
                EditChanged (ganEditOpen[nEditSearch]);
              }
            }
          }
          break;
        case 1: // Copy
          CopySelectToClip ();
          boClearSelect = TRUE;
          break;
        case 2: // Paste
          PasteClipboard ();
          EditChanged (ganEditOpen[nEditSearch]);
          break;
        case 3: // Save
          gnEmailSelected = ganEditOpen[nEditSearch];
          SetEmailSave (gnEmailSelected);
          OpenMenu ((wimp_menu *)gwhSave, nXPos + MENU_XPOS_OFFSET, nYPos);
          break;
        case 4: // Send
          gnEmailSelected = ganEditOpen[nEditSearch];
          SendEditData (gnEmailSelected);
          break;
        default: // Save drag
          if (nButton & (wimp_DRAG_SELECT | wimp_DRAG_ADJUST))
          {
            strncpy (gszSaveString, GetIconText (whWindow, ihIcon),
              sizeof(gszSaveString));
            DragBox (whWindow, ihIcon);
            geSaveType = SAVETYPE_ICONTEXT;
          }
          break;
      }
      if (boClearSelect
        && (gsSelect.nLineStart != -1)
        && (gsSelect.whWindow == gasEdit[ganEditOpen[nEditSearch]].whWindow)
        && (gsSelect.nEmailNum == ganEditOpen[nEditSearch]))
      {
        ClearSelection ();
      }
      boWinFound = TRUE;
    }
    nEditSearch++;
  }
}

//////////////////////////////////////////////////////////////////
// Poll 8: Act on user key presses
void Keys (wimp_block *pcBlock)
{
  wimp_w                      whWindow;
  wimp_i                      ihIcon;
  wimp_key_no                 nKey;
  int                         nEditSearch;
  bool                        boWinFound;
  int                         nStartLine;
  int                         nEndLine;
  bool                        boClearSelect;
  Caret                       sCaretPre;
  bool                        boShift;
  char                        szSnip[1024];
  int                         nLength;
  bool                        boUpdate;

  whWindow = pcBlock->key.w;
  ihIcon = pcBlock->key.i;
  nKey = pcBlock->key.c;
  boWinFound = FALSE;
  boShift = (osbyte1 (osbyte_SCAN_KEYBOARD, 0x80, 0) == 0xff);

  // Check for all of the edit windows
  nEditSearch = 0;
  while ((!boWinFound) && (gawhEditOpen[nEditSearch] != wimp_BACKGROUND))
  {
    if (whWindow == gawhEditOpen[nEditSearch])
    {
      boClearSelect = FALSE;
      switch (nKey)
      {
        case ('C' - 64):
          CopySelectToClip ();
          boClearSelect = TRUE;
          break;
        case ('X' - 64):
          CopySelectToClip ();
          if (boShift)
          {
            boUpdate = DeleteSection (ganEditOpen[nEditSearch], & gsSelect);
            if (ganEditOpen[nEditSearch] == gsCaret.nEditNum)
            {
              if (gsCaret.nChar == 0)
              {
                nLength = ConvertString(szSnip, Tag("Snip"), sizeof(szSnip));
              }
              else
              {
                nLength = ConvertString(szSnip, Tag("SnipMid"),
                  sizeof(szSnip));
              }
              InsertText (gsCaret.nEditNum, gsCaret.nPos, gsCaret.nLine,
                szSnip, nLength);
              boUpdate = TRUE;
            }
            if (boUpdate)
            {
              EditChanged (ganEditOpen[nEditSearch]);
              ReFormat (ganEditOpen[nEditSearch], gsCaret.nLine, -1,
                & nStartLine, & nEndLine);
              UpdateEditWindow (ganEditOpen[nEditSearch], nStartLine,
                nEndLine);
            }
          }
          else
          {
            if (DeleteSection (ganEditOpen[nEditSearch], & gsSelect))
            {
              EditChanged (ganEditOpen[nEditSearch]);
              ReFormat (ganEditOpen[nEditSearch], gsCaret.nLine, -1,
                & nStartLine, & nEndLine);
              UpdateEditWindow (ganEditOpen[nEditSearch], nStartLine,
                nEndLine);
            }
          }
          break;
        case ('V' - 64):
          PasteClipboard ();
          EditChanged (ganEditOpen[nEditSearch]);
          break;
        case ('Z' - 64):
          boClearSelect = TRUE;
          break;
        case wimp_KEY_TAB:
          DeleteSection (ganEditOpen[nEditSearch], & gsSelect);
          InsertString (ganEditOpen[nEditSearch], gsCaret.nPos,
            gsCaret.nLine, "     ");
          ReFormat (ganEditOpen[nEditSearch], gsCaret.nLine, -1,
            & nStartLine, & nEndLine);
          MoveCaretRight (5);
          UpdateEditWindow (ganEditOpen[nEditSearch], nStartLine,
            nEndLine);
          EditChanged (ganEditOpen[nEditSearch]);
          boClearSelect = TRUE;
          break;
        case wimp_KEY_INSERT:
          gasEdit[ganEditOpen[nEditSearch]].boInsert
            = !gasEdit[ganEditOpen[nEditSearch]].boInsert;
          break;
        case (wimp_KEY_RIGHT + wimp_KEY_CONTROL):
          SearchCaretRight (" \n");
          boClearSelect = TRUE;
          break;
        case (wimp_KEY_LEFT + wimp_KEY_CONTROL):
          SearchCaretLeft (" \n");
          boClearSelect = TRUE;
          break;
        case (wimp_KEY_DOWN + wimp_KEY_CONTROL):
          SearchCaretRight ("\n");
          boClearSelect = TRUE;
          break;
        case (wimp_KEY_UP + wimp_KEY_CONTROL):
          SearchCaretLeft ("\n");
          boClearSelect = TRUE;
          break;
        case (wimp_KEY_RIGHT + wimp_KEY_CONTROL + wimp_KEY_SHIFT):
          sCaretPre = gsCaret;
          SearchCaretRight (" \n");
          ShiftSelection (& sCaretPre, & gsCaret);
          break;
        case (wimp_KEY_LEFT + wimp_KEY_CONTROL + wimp_KEY_SHIFT):
          sCaretPre = gsCaret;
          SearchCaretLeft (" \n");
          ShiftSelection (& sCaretPre, & gsCaret);
          break;
        case (wimp_KEY_DOWN + wimp_KEY_CONTROL + wimp_KEY_SHIFT):
          sCaretPre = gsCaret;
          SearchCaretRight ("\n");
          ShiftSelection (& sCaretPre, & gsCaret);
          break;
        case (wimp_KEY_UP + wimp_KEY_CONTROL + wimp_KEY_SHIFT):
          sCaretPre = gsCaret;
          SearchCaretLeft ("\n");
          ShiftSelection (& sCaretPre, & gsCaret);
          break;
        case (wimp_KEY_LEFT + wimp_KEY_SHIFT):
          sCaretPre = gsCaret;
          MoveCaretLeft(1);
          ShiftSelection (& sCaretPre, & gsCaret);
          break;
        case (wimp_KEY_RIGHT + wimp_KEY_SHIFT):
          sCaretPre = gsCaret;
          MoveCaretRight(1);
          ShiftSelection (& sCaretPre, & gsCaret);
          break;
        case (wimp_KEY_UP + wimp_KEY_SHIFT):
          sCaretPre = gsCaret;
          MoveCaretUp(1);
          ShiftSelection (& sCaretPre, & gsCaret);
          break;
        case (wimp_KEY_DOWN + wimp_KEY_SHIFT):
          sCaretPre = gsCaret;
          MoveCaretDown(1);
          ShiftSelection (& sCaretPre, & gsCaret);
          break;
        case wimp_KEY_LEFT:
          MoveCaretLeft(1);
          boClearSelect = TRUE;
          break;
        case wimp_KEY_RIGHT:
          MoveCaretRight(1);
          boClearSelect = TRUE;
          break;
        case wimp_KEY_UP:
          MoveCaretUp(1);
          boClearSelect = TRUE;
          break;
        case wimp_KEY_DOWN:
          MoveCaretDown(1);
          boClearSelect = TRUE;
          break;

        case wimp_KEY_HOME:
          SetCaretPos (gsCaret.nEditNum, gsCaret.nLine, 0);
//          SearchCaretLeft ("\n");
          gsCaret.nCharPref = gsCaret.nChar;
          boClearSelect = TRUE;
          break;

        case wimp_KEY_COPY:
          if (gboIyonixDelete)
          {
            SetCaretPos (gsCaret.nEditNum, gsCaret.nLine,
              gasEdit[gsCaret.nEditNum].anLine[gsCaret.nLine + 1]
              - gasEdit[gsCaret.nEditNum].anLine[gsCaret.nLine] - 1);
            gsCaret.nCharPref = gsCaret.nChar;
//            SearchCaretRight ("\n");
          }
          else
          {
            if (!DeleteSection (ganEditOpen[nEditSearch], & gsSelect))
            {
              DeleteChar (ganEditOpen[nEditSearch], gsCaret.nPos,
                gsCaret.nLine);
            }
            EditChanged (ganEditOpen[nEditSearch]);
            ReFormat (ganEditOpen[nEditSearch], gsCaret.nLine, -1,
              & nStartLine, & nEndLine);
            UpdateEditWindow (ganEditOpen[nEditSearch], nStartLine,
              nEndLine);
          }
          boClearSelect = TRUE;
          break;

        case wimp_KEY_BACKSPACE:
//        case wimp_KEY_DELETE:
          if (!DeleteSection (ganEditOpen[nEditSearch], & gsSelect))
          {
            if (gsCaret.nPos > 0)
            {
              MoveCaretLeft(1);
              DeleteChar (ganEditOpen[nEditSearch], gsCaret.nPos,
                gsCaret.nLine);
            }
          }
          EditChanged (ganEditOpen[nEditSearch]);
          ReFormat (ganEditOpen[nEditSearch], gsCaret.nLine, -1,
            & nStartLine, & nEndLine);
          UpdateEditWindow (ganEditOpen[nEditSearch], nStartLine, nEndLine);
          boClearSelect = TRUE;
          break;

        case wimp_KEY_DELETE:
//        case wimp_KEY_COPY:
          if (!DeleteSection (ganEditOpen[nEditSearch], & gsSelect))
          {
            if (gboIyonixDelete)
            {
              DeleteChar (ganEditOpen[nEditSearch], gsCaret.nPos,
                gsCaret.nLine);
            }
            else
            {
              if (gsCaret.nPos > 0)
              {
                MoveCaretLeft(1);
                DeleteChar (ganEditOpen[nEditSearch], gsCaret.nPos,
                  gsCaret.nLine);
              }
            }
          }
          EditChanged (ganEditOpen[nEditSearch]);
          ReFormat (ganEditOpen[nEditSearch], gsCaret.nLine, -1,
            & nStartLine, & nEndLine);
          UpdateEditWindow (ganEditOpen[nEditSearch], nStartLine, nEndLine);
          boClearSelect = TRUE;
          break;



        case wimp_KEY_RETURN:
            DeleteSection (ganEditOpen[nEditSearch], & gsSelect);
            InsertChar (ganEditOpen[nEditSearch], gsCaret.nPos,
              gsCaret.nLine, '\n');
            ReFormat (ganEditOpen[nEditSearch], gsCaret.nLine, -1,
              & nStartLine, & nEndLine);
            MoveCaretRight (1);
            UpdateEditWindow (ganEditOpen[nEditSearch], nStartLine,
              nEndLine);
            EditChanged (ganEditOpen[nEditSearch]);
            boClearSelect = TRUE;
            break;
        default:
          if ((nKey >= 32) && (nKey < 127))
          {
            DeleteSection (ganEditOpen[nEditSearch], & gsSelect);
            InsertChar (ganEditOpen[nEditSearch], gsCaret.nPos,
              gsCaret.nLine, (char)nKey);
            ReFormat (ganEditOpen[nEditSearch], gsCaret.nLine, -1,
              & nStartLine, & nEndLine);
            MoveCaretRight (1);
            UpdateEditWindow (ganEditOpen[nEditSearch], nStartLine,
              nEndLine);
            EditChanged (ganEditOpen[nEditSearch]);
            boClearSelect = TRUE;
          }
          else
          {
            xwimp_process_key (nKey);
          }
          break;
      }
      if (boClearSelect
        && (gsSelect.nLineStart != -1)
        && (gsSelect.whWindow == whWindow)
        && (gsSelect.nEmailNum == ganEditOpen[nEditSearch]))
      {
        ClearSelection ();
      }
      boWinFound = TRUE;
    }
    nEditSearch++;
  }

  nEditSearch = 0;
  while ((!boWinFound) && (gawhEditOpen[nEditSearch] != wimp_BACKGROUND))
  {
    if (whWindow == gasEdit[ganEditOpen[nEditSearch]].whHead)
    {
      switch (nKey)
      {
        case wimp_KEY_RETURN:
          SetCaretPos (ganEditOpen[nEditSearch], 0, 0);
          gsCaret.nCharPref = gsCaret.nChar;
          break;
        default:
          xwimp_process_key (nKey);
          break;
      }
      boWinFound = TRUE;
    }
    nEditSearch++;
  }

  if (!boWinFound)
  {
    switch (nKey)
    {
      case wimp_KEY_RETURN:
        if (whWindow == gwhSave)
        {
          gnSaveFileType = 0xfff;
          SaveSave ();
          CloseMenu ();
        }
        if (whWindow == gwhWarn)
        {
          CloseWarning ();
        }
        if (whWindow == gwhQery)
        {
          QueryContinue ();
        }
        if (whWindow == gwhConf)
        {
          ConfigSetFromWindow ();
          CloseWindowHandle (gwhConf);
        }
        break;
      case wimp_KEY_ESCAPE:
        if (whWindow == gwhSave)
        {
          CloseMenu ();
        }
        if (whWindow == gwhWarn)
        {
          CloseWarning ();
        }
        if (whWindow == gwhQery)
        {
          QueryCancel ();
        }
        if (whWindow == gwhConf)
        {
          ConfigSetUpWindow ();
          CloseWindowHandle (gwhConf);
        }
        break;
      default:
        xwimp_process_key (nKey);
        break;
    }
  }
}

//////////////////////////////////////////////////////////////////
// Poll 9: Act on menu selections
void MenuSelect (wimp_block *pcBlock)
{
  wimp_pointer                sPointer;
  int                         nStartLine;
  int                         nEndLine;
  char                        szSnip[1024];
  int                         nLength;

  xwimp_get_pointer_info (& sPointer);

  if (gpcMenuCurrent == gpcIconBarMenu)
  {
    switch (pcBlock->selection.items[0])
    {
      case 1: // Quit
        PreQuit ();
        break;
      default:
        break;
    }
  }

  if (gpcMenuCurrent == gpcNewMenu)
  {
    switch (pcBlock->selection.items[0])
    {
      case 2: // Edit
        switch (pcBlock->selection.items[1])
        {
          case 0: // Cut
            CopySelectToClip ();
            if (gboAllowMouseCaret)
            {
              if (DeleteSection (gnEmailSelected, & gsSelect))
              {
                EditChanged (gnEmailSelected);
                ReFormat (gnEmailSelected, gsCaret.nLine, -1,
                  & nStartLine, & nEndLine);
                UpdateEditWindow (gnEmailSelected, nStartLine,
                  nEndLine);
              }
            }
            break;
          case 1: // Copy
            CopySelectToClip ();
            ClearSelection ();
            break;
          case 2: // Paste
            PasteClipboard ();
            EditChanged (gnEmailSelected);
            break;
          case 3: // Clear
            ClearSelection ();
            break;
          case 4: // Snip
            CopySelectToClip ();
            if (gboAllowMouseCaret)
            {
              DeleteSection (gnEmailSelected, & gsSelect);
              EditChanged (gnEmailSelected);
              if (gsCaret.nChar == 0)
              {
                nLength = ConvertString(szSnip, Tag("Snip"), sizeof(szSnip));
              }
              else
              {
                nLength = ConvertString(szSnip, Tag("SnipMid"),
                  sizeof(szSnip));
              }
              InsertText (gsCaret.nEditNum, gsCaret.nPos, gsCaret.nLine,
                szSnip, nLength);
              ReFormat (gnEmailSelected, gsCaret.nLine, -1,
                & nStartLine, & nEndLine);
              UpdateEditWindow (gnEmailSelected, nStartLine,
                nEndLine);
            }
            break;
          default:
            break;
        }
        break;
      case 3: // Buttons
        if (gasEdit[gnEmailSelected].boButtons)
        {
          ShowButtons (gnEmailSelected, FALSE);
        }
        else
        {
          ShowButtons (gnEmailSelected, TRUE);
        }
        break;
      case 4: // Send
        SendEditData (gnEmailSelected);
        break;
      case 5: // Quit
        PreQuit ();
        break;
      default:
        break;
    }
  }

//  if (gpcMenuCurrent == gpcLocalFontMenu)
//  {
//    ChoicesLocalFontSelect (& pcBlock->selection);
//
//    if (sPointer.buttons & wimp_CLICK_ADJUST)
//    {
//      SetUpFontMenu (gpcLocalFontMenu, gszLocalChoiceFont);
//    }
//  }
//
//  if (gpcMenuCurrent == gpcRemoteFontMenu)
//  {
//    ChoicesRemoteFontSelect (& pcBlock->selection);
//
//    if (sPointer.buttons & wimp_CLICK_ADJUST)
//    {
//      SetUpFontMenu (gpcRemoteFontMenu, gszRemoteChoiceFont);
//    }
//  }

  if (sPointer.buttons & wimp_CLICK_ADJUST)
  {
    if (gpcMenuCurrent == gpcIconBarMenu)
    {
      SetupIconbarMenu ();
    }
    if (gpcMenuCurrent == gpcNewMenu)
    {
      SetUpNewMenu ();
    }

    OpenMenu (gpcMenuCurrent, gnMenuXpos, gnMenuYpos);
  }
}

//////////////////////////////////////////////////////////////////
// Poll 7: User has finished dragging an object
void UserDragBox (wimp_block *pcBlock)
{
  char                        *szFilename;
  wimp_w                      whWindow;
  wimp_pointer                sPointer;
  wimp_message                sMessage;
  Selection                   sSelect;
  wimp_auto_scroll_info       sScrollInfo;

  pcBlock = pcBlock;

  xdragasprite_stop ();
  gboDrag = FALSE;

  xwimp_get_pointer_info (& sPointer);

  whWindow = sPointer.w;

  if ((whWindow != gwhMain)
     && (whWindow != gwhSave)
     && (whWindow != gwhWarn))
  {
    switch (geSaveType)
    {
      case SAVETYPE_EMAILS:
        sMessage.data.data_xfer.w = whWindow;
        sMessage.data.data_xfer.i = sPointer.i;
        sMessage.data.data_xfer.pos = sPointer.pos;
        sMessage.data.data_xfer.est_size = EmailSaveSize ();
        sMessage.data.data_xfer.file_type = gnSaveFileType;

        strncpy (sMessage.data.data_xfer.file_name, Tag ("SaveEmailSel"),
          211);
        sMessage.data.data_xfer.file_name[211] = 0;

        sMessage.size = WORDALIGN((44 + 1 + strlen (Tag("SaveEmailSel"))));
        sMessage.your_ref = 0;
        sMessage.action = message_DATA_SAVE;

        xwimp_send_message_to_window (wimp_USER_MESSAGE, & sMessage,
                   whWindow, sPointer.i, NULL);
        break;
      case SAVETYPE_FILE:
        sMessage.data.data_xfer.w = whWindow;
        sMessage.data.data_xfer.i = sPointer.i;
        sMessage.data.data_xfer.pos = sPointer.pos;
        sMessage.data.data_xfer.est_size = EmailSaveSize ();
        sMessage.data.data_xfer.file_type = gnSaveFileType;

        szFilename = GetIconText (gwhSave, 2);
        while (strchr (szFilename, '.'))
        {
          szFilename = strchr(szFilename, '.') + 1;
        }

        strncpy (sMessage.data.data_xfer.file_name, szFilename, 211);
        sMessage.data.data_xfer.file_name[211] = 0;

        sMessage.size = WORDALIGN((44 + 1 + strlen (szFilename)));
        sMessage.your_ref = 0;
        sMessage.action = message_DATA_SAVE;

        xwimp_send_message_to_window (wimp_USER_MESSAGE, & sMessage,
                   whWindow, sPointer.i, NULL);
        break;
      case SAVETYPE_ICONTEXT:
        sMessage.data.data_xfer.w = whWindow;
        sMessage.data.data_xfer.i = sPointer.i;
        sMessage.data.data_xfer.pos = sPointer.pos;
        sMessage.data.data_xfer.est_size = strlen(gszSaveString) + 1;
        sMessage.data.data_xfer.file_type = 0xfff;

        strncpy (sMessage.data.data_xfer.file_name, "IconText", 12);
        sMessage.data.data_xfer.file_name[11] = 0;

        sMessage.size = WORDALIGN((44 + 12));
        sMessage.your_ref = 0;
        sMessage.action = message_DATA_SAVE;

        xwimp_send_message_to_window (wimp_USER_MESSAGE, & sMessage,
          whWindow, sPointer.i, NULL);
        break;
      case SAVETYPE_SELECTCHOOSE:
        SetNullPollActive (NULLPOLL_DEACTIVE_FAST);
        FindEditCoords (gsSelect.nEmailNum, sPointer.pos.x,
          sPointer.pos.y, & sSelect.nLineEnd, & sSelect.nCharEnd);
        sSelect.nLineStart = gnSelectLineStart;
        sSelect.nCharStart = gnSelectCharStart;
        sSelect.whWindow = gsSelect.whWindow;
        sSelect.nEmailNum = gsSelect.nEmailNum;
        ChangeSelection (& sSelect);
        sScrollInfo.w = gsSelect.whWindow;
        sScrollInfo.pause_zone_sizes.x0 = 80;
        sScrollInfo.pause_zone_sizes.y0 = 80;
        sScrollInfo.pause_zone_sizes.x1 = 80;
        sScrollInfo.pause_zone_sizes.y1 = 80;
        sScrollInfo.pause_duration = 0;
        sScrollInfo.state_change = NULL;
        sScrollInfo.handle = NULL;
        xwimp_auto_scroll (0, & sScrollInfo, NULL);
        break;
      case SAVETYPE_SELECTSAVE:
        if (gpcCopySelect)
        {
          sMessage.data.data_xfer.w = whWindow;
          sMessage.data.data_xfer.i = sPointer.i;
          sMessage.data.data_xfer.pos = sPointer.pos;
          sMessage.data.data_xfer.est_size = gnCopySelectSize;
          sMessage.data.data_xfer.file_type = 0xfff;

          strncpy (sMessage.data.data_xfer.file_name,
            Tag ("SaveSel"), 12);
          sMessage.data.data_xfer.file_name[11] = 0;

          sMessage.size = WORDALIGN((44 + 12));
          sMessage.your_ref = 0;
          sMessage.action = message_DATA_SAVE;

          xwimp_send_message_to_window (wimp_USER_MESSAGE, & sMessage,
            whWindow, sPointer.i, NULL);
        }
        break;
      case SAVETYPE_COLUMN:
        SetNullPollActive (NULLPOLL_DEACTIVE_FAST);
        break;
      default:
        break;
    }
  }
}

//////////////////////////////////////////////////////////////////
// Open a window pane
void OpenWindowPane (PANE ePaneType, wimp_w whPane, wimp_open * psMainOpen, wimp_open * psPaneOpen, int nHeight)
{
  switch (ePaneType)
  {
    default:
    case PANE_TOP:
      *psPaneOpen = *psMainOpen;
      psPaneOpen->w = whPane;
      xwimp_open_window (psPaneOpen);
      psMainOpen->next = psPaneOpen->w;
      break;
    case PANE_BASE:
      *psPaneOpen = *psMainOpen;
      psPaneOpen->visible.y1 = psMainOpen->visible.y0 + nHeight;
      psPaneOpen->w = whPane;
      xwimp_open_window (psPaneOpen);
      psMainOpen->next = psPaneOpen->w;
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Poll 2: Open window given window block
void OpenWindow (wimp_block *pcBlock)
{
  wimp_w                      whWindow;
  bool                        boWinFound;
  int                         nEmailSearch;
  wimp_open                   sPaneOpen;
  PANE                        ePaneType;
  int                         nHeight = 0;

  whWindow = pcBlock->open.w;

  ePaneType = PANE_NONE;
  boWinFound = FALSE;

  nEmailSearch = 0;
  while ((!boWinFound) && (gawhEditOpen[nEmailSearch] != wimp_BACKGROUND))
  {
    if (whWindow == gawhEditOpen[nEmailSearch])
    {
      if (gasEdit[ganEditOpen[nEmailSearch]].boButtons)
      {
        sPaneOpen = pcBlock->open;
        sPaneOpen.w = gasEdit[ganEditOpen[nEmailSearch]].whHead;
        xwimp_open_window (& sPaneOpen);
        pcBlock->open.next = sPaneOpen.w;
        ePaneType = PANE_TOP;
      }
      boWinFound = TRUE;
    }
    nEmailSearch++;
  }
  xwimp_open_window (& pcBlock->open);

  if (ePaneType != PANE_NONE)
  {
    switch (ePaneType)
    {
      case PANE_TOP:
        // Check to see whether the pane is in the correct position
        if ((sPaneOpen.visible.x0 != pcBlock->open.visible.x0)
          || (sPaneOpen.visible.y1 != pcBlock->open.visible.y1)
          || (sPaneOpen.visible.x1 != pcBlock->open.visible.x1))
        {
          sPaneOpen.visible = pcBlock->open.visible;
          xwimp_open_window (& sPaneOpen);
        }
        break;
      case PANE_BASE:
        // Check to see whether the pane is in the correct position
        if ((sPaneOpen.visible.x0 != pcBlock->open.visible.x0)
          || (sPaneOpen.visible.y0 != pcBlock->open.visible.y0)
          || (sPaneOpen.visible.x1 != pcBlock->open.visible.x1))
        {
          sPaneOpen.visible = pcBlock->open.visible;
          sPaneOpen.visible.y1 = pcBlock->open.visible.y0 + nHeight;
          xwimp_open_window (& sPaneOpen);
        }
        break;
      default:
        // Do nothing
        break;
    }
  }
}

//////////////////////////////////////////////////////////////////
// Open a window on screen that is currently closed
void OpenWindowInit (wimp_w whWindow)
{
  wimp_window_state           sState;

  sState.w = whWindow;
  xwimp_get_window_state (& sState);
  sState.next = wimp_TOP;
  xwimp_open_window ((wimp_open *)& sState);
}

//////////////////////////////////////////////////////////////////
// Open a window on screen that is currently closed with its pane
void OpenWindowInitPane (wimp_w whWindow, wimp_w whPane, PANE ePaneType)
{
  wimp_window_state           sState;
  wimp_window_state           sPane;

  sState.w = whWindow;
  xwimp_get_window_state (& sState);
  sState.next = wimp_TOP;
  xwimp_open_window ((wimp_open *)& sState);

  switch (ePaneType)
  {
    default:
    case PANE_TOP:
      sState.w = whPane;
      xwimp_open_window ((wimp_open *)& sState);
      break;
    case PANE_BASE:
      sPane.w = whPane;
      xwimp_get_window_state (& sPane);
      sState.w = whPane;
      sState.visible.y1
        = sState.visible.y0 + (sPane.visible.y1 - sPane.visible.y0);
      xwimp_open_window ((wimp_open *)& sState);
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Open a window on screen that is currently closed with its pane
void OpenWindowInitPaneNew (wimp_w whWindow, wimp_w whPane, PANE ePaneType)
{
  wimp_window_state           sState;
  wimp_window_state           sPane;

  sState.w = whWindow;
  xwimp_get_window_state (& sState);
  sState.yscroll = 0;
  sState.next = wimp_TOP;
  xwimp_open_window ((wimp_open *)& sState);

  switch (ePaneType)
  {
    default:
    case PANE_TOP:
      sState.w = whPane;
      xwimp_open_window ((wimp_open *)& sState);
      break;
    case PANE_BASE:
      sPane.w = whPane;
      xwimp_get_window_state (& sPane);
      sState.w = whPane;
      sState.visible.y1
        = sState.visible.y0 + (sPane.visible.y1 - sPane.visible.y0);
      xwimp_open_window ((wimp_open *)& sState);
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Open a window on screen that is currently closed
void OpenWindowInitNew (wimp_w whWindow)
{
  wimp_window_state           sState;

  sState.w = whWindow;
  xwimp_get_window_state (& sState);
  sState.yscroll = 0;
  sState.next = wimp_TOP;
  xwimp_open_window ((wimp_open *)& sState);
}

//////////////////////////////////////////////////////////////////
// Open a window in the centre of the screen
void OpenWindowInitCentre (wimp_w whWindow)
{
  int                         nHeight;
  int                         nWidth;
  int                         nEigFactor;
  int                         nSize;
  int                         nPosition;
  wimp_window_state           sState;

  sState.w = whWindow;
  xwimp_get_window_state (& sState);
  sState.next = wimp_TOP;

  nWidth = sState.visible.x1 - sState.visible.x0;
  nHeight = sState.visible.y1 - sState.visible.y0;

  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_XEIG_FACTOR,
    & nEigFactor, NULL);
  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_XWIND_LIMIT,
    & nSize, NULL);
  nPosition = ((nSize << nEigFactor) - nWidth) / 2;
  sState.visible.x0 = nPosition;
  sState.visible.x1 = nPosition + nWidth;

  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_YEIG_FACTOR,
    & nEigFactor, NULL);
  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_YWIND_LIMIT,
    & nSize, NULL);
  nPosition = ((nSize << nEigFactor) - nHeight) / 2;
  sState.visible.y0 = nPosition;
  sState.visible.y1 = nPosition + nHeight;

  xwimp_open_window ((wimp_open *)& sState);
}

//////////////////////////////////////////////////////////////////
// Open a window in the centre of the screen with a pane
void OpenWindowInitPaneCentreSize (wimp_w whWindow, wimp_w whPane, PANE ePaneType, int nWidth, int nHeight)
{
//  int                         nHeight;
//  int                         nWidth;
  int                         nEigFactor;
  int                         nSize;
  int                         nPosition;
  wimp_window_state           sState;
  wimp_window_state           sPane;

  sState.w = whWindow;
  xwimp_get_window_state (& sState);
  sState.next = wimp_TOP;
  sState.xscroll = 0;
  sState.yscroll = 0;

//  nWidth = sState.visible.x1 - sState.visible.x0;
//  nHeight = sState.visible.y1 - sState.visible.y0;

  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_XEIG_FACTOR,
    & nEigFactor, NULL);
  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_XWIND_LIMIT,
    & nSize, NULL);
  nPosition = ((nSize << nEigFactor) - nWidth) / 2;
  sState.visible.x0 = nPosition;
  sState.visible.x1 = nPosition + nWidth;

  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_YEIG_FACTOR,
    & nEigFactor, NULL);
  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_YWIND_LIMIT,
    & nSize, NULL);
  nPosition = ((nSize << nEigFactor) - nHeight) / 2;
  sState.visible.y0 = nPosition;
  sState.visible.y1 = nPosition + nHeight;

  xwimp_open_window ((wimp_open *)& sState);

  switch (ePaneType)
  {
    default:
    case PANE_TOP:
      sState.w = whPane;
      xwimp_open_window ((wimp_open *)& sState);
      break;
    case PANE_BASE:
      sPane.w = whPane;
      xwimp_get_window_state (& sPane);
      sState.w = whPane;
      sState.visible.y1
        = sState.visible.y0 + (sPane.visible.y1 - sPane.visible.y0);
      xwimp_open_window ((wimp_open *)& sState);
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Open a given menu at the given position on screen
void OpenMenu (wimp_menu * pcMenu, int nXPos, int nYPos)
{
  xwimp_create_menu (pcMenu, nXPos, nYPos);

  gnMenuXpos = nXPos;
  gnMenuYpos = nYPos;
  gpcMenuCurrent = pcMenu;
}

//////////////////////////////////////////////////////////////////
// Poll 3: Close a window
void CloseWindow (wimp_block *pcBlock)
{
  wimp_w                      whWindow;
  bool                        boWinFound;
  int                         nEmailSearch;

  whWindow = pcBlock->open.w;

  boWinFound = FALSE;

  nEmailSearch = 0;
  while ((!boWinFound) && (gawhEditOpen[nEmailSearch] != wimp_BACKGROUND))
  {
    if (whWindow == gawhEditOpen[nEmailSearch])
    {
      if ((gasEdit[ganEditOpen[nEmailSearch]].boChanged) && (gboWarnOnClose))
      {
        gwhWindowClose = whWindow;
        ShowQueryTagVolatile ("Ms6", "MsDiscard", "MsCancel",
          QUERYACTION_CLOSEEDIT);
      }
      else
      {
        err (xwimp_close_window (whWindow));
        err (xwimp_close_window (gasEdit[ganEditOpen[nEmailSearch]].whHead));
        DestroyEditWindow (ganEditOpen[nEmailSearch]);
      }
      boWinFound = TRUE;
    }
    nEmailSearch++;
  }

  if (!boWinFound)
  {
    wimp_close_window (whWindow);
  }
}

//////////////////////////////////////////////////////////////////
// Close window given handle
void CloseWindowHandle (wimp_w whWindow)
{
  wimp_close_window (whWindow);
}

//////////////////////////////////////////////////////////////////
// Close window given handle and its pane
void CloseWindowHandlePane (wimp_w whWindow, wimp_w whPane)
{
  wimp_close_window (whWindow);
  wimp_close_window (whPane);
}

//////////////////////////////////////////////////////////////////
// Close the currently open menu
void CloseMenu (void)
{
  xwimp_create_menu ((wimp_menu*)-1, 0, 0);

  gnMenuXpos = 0;
  gnMenuYpos = 0;
  gpcMenuCurrent = NULL;
}

//////////////////////////////////////////////////////////////////
// Create an iconbar icon
wimp_i CreateIconbarIcon (char * szSprite, int nWidth, int nHeight)
{
  wimp_i                      ihIcon;
  wimp_icon_create            sIconBlock;

  sIconBlock.w = wimp_ICON_BAR_RIGHT;
  sIconBlock.icon.extent.x0 = 0;
  sIconBlock.icon.extent.y0 = 0;
  sIconBlock.icon.extent.x1 = nWidth;
  sIconBlock.icon.extent.y1 = nHeight;
  sIconBlock.icon.flags = wimp_ICON_SPRITE |
                   (wimp_BUTTON_CLICK << wimp_ICON_BUTTON_TYPE_SHIFT);
  strncpy(sIconBlock.icon.data.sprite, szSprite, 12);

  ihIcon = wimp_create_icon (& sIconBlock);

  return ihIcon;
}

//////////////////////////////////////////////////////////////////
// Create window from Templates file
wimp_w LoadTemplate (char * szWindowTitle)
{
  wimp_window                 *pcWindow;
  char *                      pcIndirected;
  int                         nWindowSize;
  int                         nIndirectedSize;
  char                        szTitle[12];
  wimp_w                      whWindow;

  wimp_load_template (0, NULL, NULL, NULL, szWindowTitle, 0,
    & nWindowSize, & nIndirectedSize);

  pcWindow = (wimp_window*)malloc (nWindowSize);
  pcIndirected = malloc (nIndirectedSize);
  strncpy (szTitle, szWindowTitle, 12);

  wimp_load_template (pcWindow, pcIndirected, pcIndirected + nIndirectedSize,
    gacFontRef, szWindowTitle, 0, NULL, NULL);

  whWindow = wimp_create_window (pcWindow);

  free((void *)pcWindow);

  return whWindow;
}

//////////////////////////////////////////////////////////////////
// Create window from Templates file with sprites
wimp_w LoadTemplateSprites (char * szWindowTitle, osspriteop_area * pcSpriteArea)
{
  wimp_window                 *pcWindow;
  char *                      pcIndirected;
  int                         nWindowSize;
  int                         nIndirectedSize;
  char                        szTitle[12];
  wimp_w                      whWindow;

  wimp_load_template (0, NULL, NULL, NULL, szWindowTitle, 0,
    & nWindowSize, & nIndirectedSize);

  pcWindow = (wimp_window*)malloc (nWindowSize);
  pcIndirected = malloc (nIndirectedSize);
  strncpy (szTitle, szWindowTitle, 12);

  wimp_load_template (pcWindow, pcIndirected, pcIndirected + nIndirectedSize,
    gacFontRef, szWindowTitle, 0, NULL, NULL);

  if (pcSpriteArea)
  {
    pcWindow->sprite_area = pcSpriteArea;
  }

  whWindow = wimp_create_window (pcWindow);

  free((void *)pcWindow);

  return whWindow;
}

//////////////////////////////////////////////////////////////////
// Create a menu from a text string - usually kept in the messages file
wimp_menu * CreateMenu (char * szMenu)
{
  wimp_menu                   *pcMenu;
  char *                      pcBuffer;
  char *                      nAt = 0;
  char *                      nAt2 = 0;
  int                         nWidth = 0;
  wimp_menu_flags             uFlags;
  char                        szMenuString[512];
  char *                      pcMenuString;
  int                         nMenuItems;
  char                        *szItem;
  int                         nMenuEntry;
  int                         nStringLen;

  nMenuItems = -1;
  szItem = szMenu;
  while (szItem)
  {
    nMenuItems++;
    szItem = strchr(szItem + 1, ',');
  }

  pcMenu = (wimp_menu*)malloc (wimp_SIZEOF_MENU(nMenuItems));
  pcBuffer = (char *)malloc (strlen(szMenu));

  nAt = strchr (szMenu, ',');
  if (!nAt)
  {
    nAt = (char *)strlen (szMenu);
  }
  else
  {
    nAt -= (int)szMenu;
  }
  strncpy (szMenuString, szMenu, (int)nAt);
  szMenuString[(int)nAt] = 0;

  if (strlen (szMenuString) >= 12)
  {
    strcpy (pcBuffer, szMenuString);
    pcMenu->title_data.indirected_text.text = pcBuffer;
    pcBuffer += strlen (szMenuString) + 1;
    uFlags = wimp_MENU_TITLE_INDIRECTED;
  }
  else
  {
    strncpy (pcMenu->title_data.text, szMenuString, 12);
    uFlags = 0u;
  }
  pcMenu->title_fg = wimp_COLOUR_BLACK;
  pcMenu->title_bg = wimp_COLOUR_LIGHT_GREY;
  pcMenu->work_fg = wimp_COLOUR_BLACK;
  pcMenu->work_bg = wimp_COLOUR_WHITE;

  pcMenu->height = 44;
  pcMenu->gap = 0;

  nMenuEntry = 0;
  do
  {
    pcMenu->entries[nMenuEntry].sub_menu = (wimp_menu*)-1;
    pcMenu->entries[nMenuEntry].icon_flags = wimp_ICON_TEXT
                   | wimp_ICON_FILLED
                   | (wimp_COLOUR_BLACK << wimp_ICON_FG_COLOUR_SHIFT);
    nAt2 = nAt + 1;

    nAt = strchr (szMenu + (int)nAt2, ',');
    if (!nAt)
    {
      nAt = (char *)strlen (szMenu) + 1;
    }
    else
    {
      nAt -= (int)szMenu;
    }
    strncpy (szMenuString, szMenu + (int)nAt2, (int)nAt - (int)nAt2);
    szMenuString[(int)nAt - (int)nAt2] = 0;

    pcMenuString = szMenuString;

    if (pcMenuString[0] == '+')
    {
      uFlags = wimp_MENU_TICKED;
      pcMenuString++;
    }
    if (pcMenuString[0] == '-')
    {
      uFlags |= wimp_MENU_SEPARATE;
      pcMenuString++;
    }
    if (pcMenuString[0] == '|')
    {
      uFlags |= wimp_MENU_WRITABLE;
      pcMenuString++;
    }
    if (pcMenuString[0] == '>')
    {
      pcMenuString++;
      if (gnMenuWins < (int)sizeof(gawhMenuWinHandle))
      {
        gawhMenuWinHandle[gnMenuWins] = LoadTemplate (pcMenuString);
        pcMenu->entries[nMenuEntry].sub_menu
                   = (wimp_menu*)gawhMenuWinHandle[gnMenuWins];

        gnMenuWins++;
      }
    }

    nStringLen = (int)strlen (pcMenuString);
    if (strlen (pcMenuString) >= 12)
    {
      pcMenu->entries[nMenuEntry].icon_flags |= wimp_ICON_INDIRECTED;
      pcMenu->entries[nMenuEntry].data.indirected_text.text = pcBuffer;
      pcMenu->entries[nMenuEntry].data.indirected_text.validation = NULL;
      pcMenu->entries[nMenuEntry].data.indirected_text.size = nStringLen + 1;
      strcpy (pcBuffer, pcMenuString);
      pcBuffer += nStringLen + 1;
    }
    else
    {
      strncpy (pcMenu->entries[nMenuEntry].data.text, pcMenuString, 12);
    }

    pcMenu->entries[nMenuEntry].menu_flags = uFlags;
    if (nStringLen > nWidth)
    {
      nWidth = nStringLen;
    }
    nMenuEntry++;
    uFlags = 0u;
  } while ((int)nAt < (int)strlen (szMenu));

  pcMenu->width = (nWidth + 1) * 16;
  pcMenu->entries[nMenuEntry - 1].menu_flags |= wimp_MENU_LAST;

  return pcMenu;
}

//////////////////////////////////////////////////////////////////
// Get the text string from an icon
char * GetIconText (wimp_w whWindow, wimp_i ihIcon)
{
  wimp_icon_state             sIconState;
  char                        *szString;
  int                         nTerminate = 0;

  sIconState.w = whWindow;
  sIconState.i = ihIcon;
  xwimp_get_icon_state (& sIconState);

  if (sIconState.icon.flags & wimp_ICON_INDIRECTED)
  {
    szString = sIconState.icon.data.indirected_text.text;
    while (szString[nTerminate] >= 32)
    {
      nTerminate++;
    }
    szString[nTerminate] = 0;
  }
  else
  {
    szString = sIconState.icon.data.text;
    while ((szString[nTerminate] >= 32) && (nTerminate < 12))
    {
      nTerminate++;
    }
    szString[nTerminate] = 0;
  }
  return szString;
}

//////////////////////////////////////////////////////////////////
// Set the text string for an icon
void SetIconText (char * szText, wimp_w whWindow, wimp_i ihIcon)
{
  wimp_icon_state             sIconState;

  sIconState.w = whWindow;
  sIconState.i = ihIcon;
  xwimp_get_icon_state (& sIconState);

  if (sIconState.icon.flags & wimp_ICON_INDIRECTED)
  {
    strcpy (sIconState.icon.data.indirected_text.text, szText);
  }
  else
  {
    strncpy (sIconState.icon.data.text, szText, 12);
    sIconState.icon.data.text[11] = 0;
  }

  xwimp_set_icon_state (whWindow, ihIcon, 0, 0);
}

//////////////////////////////////////////////////////////////////
// Set the text string for an icon and increment postfixed numbers
void SetIconTextIncrement (char * szText, wimp_w whWindow, wimp_i ihIcon)
{
  wimp_icon_state             sIconState;
  int                         nNumPos;
  char                        *szIconText;
  bool                        boCarry;

  sIconState.w = whWindow;
  sIconState.i = ihIcon;
  xwimp_get_icon_state (& sIconState);

  nNumPos = strlen (szText) - 1;

  if (sIconState.icon.flags & wimp_ICON_INDIRECTED)
  {
    szIconText = sIconState.icon.data.indirected_text.text;
    strcpy (sIconState.icon.data.indirected_text.text, szText);
  }
  else
  {
    szIconText = sIconState.icon.data.text;
    strncpy (sIconState.icon.data.text, szText, 12);
    sIconState.icon.data.text[11] = 0;
    if (nNumPos > 11)
    {
      nNumPos = 11;
    }
  }

  // Increment any postfixed numbers
  boCarry = TRUE;
  while ((nNumPos >= 0)  && boCarry
    && ((szIconText[nNumPos] >= '0') && (szIconText[nNumPos] <= '9')))
  {
    if (szIconText[nNumPos] < '9')
    {
      szIconText[nNumPos]++;
      boCarry = FALSE;
    }
    else
    {
      szIconText[nNumPos] = '0';
      boCarry = TRUE;
    }
    nNumPos--;
  }

  xwimp_set_icon_state (whWindow, ihIcon, 0, 0);
}

//////////////////////////////////////////////////////////////////
// Increment postfixed numbers to icon text
void IconTextIncrement (wimp_w whWindow, wimp_i ihIcon)
{
  wimp_icon_state             sIconState;
  int                         nNumPos;
  char                        *szIconText;
  bool                        boCarry;

  sIconState.w = whWindow;
  sIconState.i = ihIcon;
  xwimp_get_icon_state (& sIconState);

  if (sIconState.icon.flags & wimp_ICON_INDIRECTED)
  {
    szIconText = sIconState.icon.data.indirected_text.text;
  }
  else
  {
    szIconText = sIconState.icon.data.text;
  }

  nNumPos = strlen (szIconText) - 1;

  // Increment any postfixed numbers
  boCarry = TRUE;
  while ((nNumPos >= 0)  && boCarry
    && ((szIconText[nNumPos] >= '0') && (szIconText[nNumPos] <= '9')))
  {
    if (szIconText[nNumPos] < '9')
    {
      szIconText[nNumPos]++;
      boCarry = FALSE;
    }
    else
    {
      szIconText[nNumPos] = '0';
      boCarry = TRUE;
    }
    nNumPos--;
  }

  xwimp_set_icon_state (whWindow, ihIcon, 0, 0);
}

//////////////////////////////////////////////////////////////////
// Set the sprite string for an icon
void SetIconSprite (char * szSprite, wimp_w whWindow, wimp_i ihIcon)
{
  wimp_icon_state             sIconState;

  sIconState.w = whWindow;
  sIconState.i = ihIcon;
  xwimp_get_icon_state (& sIconState);

  if (sIconState.icon.flags & wimp_ICON_INDIRECTED)
  {
    strcpy ((char*)(sIconState.icon.data.indirected_sprite.id), szSprite);
  }
  else
  {
    strncpy (sIconState.icon.data.sprite, szSprite, 12);
    sIconState.icon.data.sprite[11] = 0;
  }

  xwimp_set_icon_state (whWindow, ihIcon, 0, 0);
}

//////////////////////////////////////////////////////////////////
// Get the selection state from an icon
bool GetIconSelectionState (wimp_w whWindow, wimp_i ihIcon)
{
  wimp_icon_state             sIconState;
  bool                        boState;

  sIconState.w = whWindow;
  sIconState.i = ihIcon;
  xwimp_get_icon_state (& sIconState);

  if (sIconState.icon.flags & wimp_ICON_SELECTED)
  {
    boState = TRUE;
  }
  else
  {
    boState = FALSE;
  }

  return boState;
}

//////////////////////////////////////////////////////////////////
// Set the selection state from an icon
void SetIconSelectionState (bool boState, wimp_w whWindow, wimp_i ihIcon)
{
  wimp_icon_flags             uFlagsEOR;

  if (boState)
  {
    uFlagsEOR = wimp_ICON_SELECTED;
  }
  else
  {
    uFlagsEOR = 0;
  }

  xwimp_set_icon_state (whWindow, ihIcon, uFlagsEOR, wimp_ICON_SELECTED);
}

//////////////////////////////////////////////////////////////////
// Grey out or ungrey an icon
void SetIconGreyness (bool boState, wimp_w whWindow, wimp_i ihIcon)
{
  wimp_icon_flags             uFlagsEOR;

  if (boState)
  {
    uFlagsEOR = wimp_ICON_SHADED;
  }
  else
  {
    uFlagsEOR = 0;
  }

  xwimp_set_icon_state (whWindow, ihIcon, uFlagsEOR, wimp_ICON_SHADED);
}

//////////////////////////////////////////////////////////////////
// Sender wants to send data to the receiver
// Normal use: user has terminated a drag, so this message is sent
// Response:   DataSaveAck
//             RamFetch
void DataSave (wimp_block *pcBlock)
{
  int                         nEditSearch;
  bool                        boWinFound;
  wimp_w                      whWindow;
  int                         nLine;
  int                         nChar;
  RELPOS                      eRelPos;

  whWindow = pcBlock->message.data.data_xfer.w;

  // Find out if it was saved in to an Edit window
  boWinFound = FALSE;
  nEditSearch = 0;
  while ((!boWinFound) && (gawhEditOpen[nEditSearch] != wimp_BACKGROUND))
  {
    // Check for a drag to the main part of an edit window

    // Check filetype
    if ((pcBlock->message.data.data_xfer.file_type == 0xfff)
      && (gboAllowMouseCaret))
    {
      if (whWindow == gawhEditOpen[nEditSearch])
      {
        eRelPos = PointRelativeSelection (ganEditOpen[nEditSearch],
          pcBlock->message.data.data_xfer.pos.x,
          pcBlock->message.data.data_xfer.pos.y,
          & gsSelect, & nLine, & nChar);

        geLoadType = LOADTYPE_EDITTEXT;

        if ((eRelPos == RELPOS_WITHINSTART)
          || (eRelPos == RELPOS_WITHINEND))
        {
          gboDelete = TRUE;
        }
        else
        {
          gboDelete = FALSE;
        }

        SetCaretPos (ganEditOpen[nEditSearch], nLine, nChar);
        gsCaret.nCharPref = gsCaret.nChar;

        // Check the size
/*
        pcBlock->message.size = 60;
        pcBlock->message.your_ref = pcBlock->message.my_ref;
        pcBlock->message.action = message_DATA_SAVE_ACK;
        strcpy (pcBlock->message.data.data_xfer.file_name, "<Wimp$Scrap>");


        xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
          pcBlock->message.sender);
*/
        // Make a copy in case the RamFetch returns;
        gsMessageStore = pcBlock->message.data.data_xfer;
        gthSender = pcBlock->message.sender;

        pcBlock->message.size = 28;
        pcBlock->message.your_ref = pcBlock->message.my_ref;
        pcBlock->message.action = message_RAM_FETCH;
        pcBlock->message.data.ram_xfer.addr = gpcRamTransBuffer;
        pcBlock->message.data.ram_xfer.size = RAMTRANSMIT_SIZE;

        xwimp_send_message (wimp_USER_MESSAGE_RECORDED, & pcBlock->message,
          pcBlock->message.sender);

        gnMyRef = pcBlock->message.my_ref;

        boWinFound = TRUE;
      }
    }

    nEditSearch++;
  }
}

//////////////////////////////////////////////////////////////////
// RamFetch Acknowledge received - try DataSaveAck instead
// Usually followed by a DataLoad message
void RamFetchReturned (wimp_block *pcBlock)
{
  if (gnMyRef == pcBlock->message.my_ref)
  {
    // Use the copy made earlier during the DataSave
    pcBlock->message.data.data_xfer = gsMessageStore;

    pcBlock->message.size = 60;
    pcBlock->message.your_ref = pcBlock->message.my_ref;
    pcBlock->message.action = message_DATA_SAVE_ACK;
    strcpy (pcBlock->message.data.data_xfer.file_name, "<Wimp$Scrap>");

    xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
      gthSender);
  }
}

//////////////////////////////////////////////////////////////////
// Acknowledge that you received a DataSave message
// Usually followed by a DataLoad message
void DataSaveAck (wimp_block *pcBlock)
{
  char                        *szFilename;
  int                         nStringLen;

  CloseMenu ();

  switch (geSaveType)
  {
      case SAVETYPE_EMAILS:
        szFilename = pcBlock->message.data.data_xfer.file_name;

        // Save the file
        SaveFile (szFilename);

        pcBlock->message.your_ref = pcBlock->message.my_ref;
        pcBlock->message.action = message_DATA_LOAD;

        xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
          pcBlock->message.sender);
        break;
      case SAVETYPE_FILE:
        szFilename = pcBlock->message.data.data_xfer.file_name;
        // Save the file to the given filename
        SetIconTextIncrement (szFilename, gwhSave, 2);

        // Save the file
        SaveFile (szFilename);

        pcBlock->message.your_ref = pcBlock->message.my_ref;
        pcBlock->message.action = message_DATA_LOAD;

        xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
          pcBlock->message.sender);
        break;
      case SAVETYPE_ICONTEXT:
        szFilename = pcBlock->message.data.data_xfer.file_name;
        // Save the file to the given filename
        nStringLen = strlen (gszSaveString);

        err (xosfile_save_stamped (szFilename, 0xfff, gszSaveString,
          (gszSaveString + nStringLen)));

        pcBlock->message.your_ref = pcBlock->message.my_ref;
        pcBlock->message.action = message_DATA_LOAD;

        xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
          pcBlock->message.sender);
        break;
      case SAVETYPE_SELECTSAVE:
        szFilename = pcBlock->message.data.data_xfer.file_name;

        if (gpcCopySelect)
        {
          // Save the file to the given filename
          err (xosfile_save_stamped (szFilename, 0xfff, gpcCopySelect,
            (gpcCopySelect + gnCopySelectSize)));

          pcBlock->message.your_ref = pcBlock->message.my_ref;
          pcBlock->message.action = message_DATA_LOAD;

          xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
            pcBlock->message.sender);
        }
        break;
      case SAVETYPE_CLIPBOARD:
        szFilename = pcBlock->message.data.data_xfer.file_name;

        if (gpcClipboard)
        {
          // Save the file to the given filename
          err (xosfile_save_stamped (szFilename, 0xfff, gpcClipboard,
            (gpcClipboard + gnClipboardSize)));

          pcBlock->message.your_ref = pcBlock->message.my_ref;
          pcBlock->message.action = message_DATA_LOAD;

          xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
            pcBlock->message.sender);
        }
        break;
      default:
        break;
  }
}

//////////////////////////////////////////////////////////////////
// Receiver of this message should load the file
// and answer with DataSaveAck if successful
void DataLoad (wimp_block *pcBlock)
{
  char                        *szFilename;
  char                        *pcLoadedFile;
  int                         nSize;
  int                         nStartLine;
  int                         nEndLine;
  RELPOS                      eRelPos;
  bool                        boWinFound;
  int                         nEditSearch;
  wimp_w                      whWindow;
  int                         nLine;
  int                         nChar;
  int                         nLoadSuccess;
  bool                        boUseAttributes;
  unsigned int                uLoad;
  unsigned int                uExec;
  unsigned int                uAccess;
  os_error                    *psError;

  szFilename = pcBlock->message.data.data_xfer.file_name;

  // Load the file from the given filename
//  pcLoadedFile = LoadFile (szFilename, & nSize);

  nLoadSuccess = LoadFileFlex (szFilename, & pcLoadedFile, & nSize);

  if ((pcLoadedFile) && (nLoadSuccess == 1))
  {
    // Get the file attributes
    psError = xosfile_read (szFilename, NULL, & uLoad, & uExec, NULL,
      & uAccess);
    if (psError)
    {
      err (psError);
      boUseAttributes = FALSE;
    }
    else
    {
      boUseAttributes = TRUE;
    }

    // Delete the file
    if (strcmp (szFilename, "<Wimp$Scrap>") == 0)
    {
      err (xosfile_delete (szFilename, NULL, NULL, NULL, NULL, NULL));
    }

    whWindow = pcBlock->message.data.data_xfer.w;

    // Find out if it was saved in to an Edit window
    boWinFound = FALSE;

    nEditSearch = 0;
    while ((!boWinFound) && (gawhEditOpen[nEditSearch] != wimp_BACKGROUND))
    {
      // Check filetype
      if (pcBlock->message.data.data_xfer.file_type == 0xfff)
      {
        if (whWindow == gawhEditOpen[nEditSearch])
        {
          eRelPos = PointRelativeSelection (ganEditOpen[nEditSearch],
            pcBlock->message.data.data_xfer.pos.x,
            pcBlock->message.data.data_xfer.pos.y,
            & gsSelect, & nLine, & nChar);

          if ((eRelPos == RELPOS_WITHINSTART)
            || (eRelPos == RELPOS_WITHINEND))
          {
            SetCaretPos (ganEditOpen[nEditSearch], gsSelect.nLineStart,
            gsSelect.nCharStart);
            gsCaret.nCharPref = gsCaret.nChar;
            DeleteSection (gsCaret.nEditNum, & gsSelect);
          }
          else
          {
            SetCaretPos (ganEditOpen[nEditSearch], nLine, nChar);
            gsCaret.nCharPref = gsCaret.nChar;
          }

          InsertTextFlex (gsCaret.nEditNum, gsCaret.nPos, gsCaret.nLine,
            & pcLoadedFile, nSize);
          ReFormat (gsCaret.nEditNum, gsCaret.nLine, -1,
            & nStartLine, & nEndLine);
          MoveCaretRight (nSize);
          UpdateEditWindow (gsCaret.nEditNum, nStartLine,
            nEndLine);
          EditChanged (ganEditOpen[nEditSearch]);
          ClearSelection ();

          gnMyRef = pcBlock->message.my_ref;

          // Loaded successfully, so respond with a DataLoadAck
          pcBlock->message.your_ref = pcBlock->message.my_ref;
          pcBlock->message.action = message_DATA_LOAD_ACK;

          xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
            pcBlock->message.sender);

          boWinFound = TRUE;
        }
      }

      nEditSearch++;
    }

//    free (pcLoadedFile);
    if (pcLoadedFile)
    {
      flex_free ((flex_ptr)(& pcLoadedFile));
    }
  }
}

////////////////////////////////////////////////////////////////////
//// User has double clicked on a file
//void DataOpen (wimp_block *pcBlock)
//{
//  char                        *szFilename;
//  char                        *pcLoadedFile;
//  int                         nSize;
//
//  // Check filetype
//  if (pcBlock->message.data.data_xfer.file_type == 0xffd)
//  {
//    szFilename = pcBlock->message.data.data_xfer.file_name;
//
//    // Load the file from the given filename
//    pcLoadedFile = LoadFile (szFilename, & nSize);
//
//    if (pcLoadedFile)
//    {
//      pcBlock->message.your_ref = pcBlock->message.my_ref;
//      pcBlock->message.action = message_DATA_LOAD_ACK;
//
//      xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
//        pcBlock->message.sender);
//
//      FileLoaded (pcLoadedFile, nSize);
//    }
//  }
//}

//////////////////////////////////////////////////////////////////
// Copy the data from here to the external program
// Reply with RamTransmit
void RamFetch (wimp_block *pcBlock)
{
  wimp_t                      nDestHandle;
  int                         nSize;
  int                         nSendLen;

  switch (geSaveType)
  {
    case SAVETYPE_FILE:
    case SAVETYPE_EMAILS:
      break;
    default:
      nSize = pcBlock->message.data.ram_xfer.size;
      nDestHandle = pcBlock->message.sender;

      if (gnRamTransRef != pcBlock->message.your_ref)
      {
        // First in a series of RAM transfers
        CloseMenu ();
        switch (geSaveType)
        {
          case SAVETYPE_EMAILS:
          case SAVETYPE_FILE:
            // Dealt with in RamFetchEmails ()
            break;
          case SAVETYPE_ICONTEXT:
            gpcRamTransPos = gszSaveString;
            gnRamTransLeft = (int)strlen (gszSaveString);
            gnRamTransRef = pcBlock->message.your_ref;
            break;
          case SAVETYPE_SELECTSAVE:
            if (gpcCopySelect)
            {
              gpcRamTransPos = gpcCopySelect;
              gnRamTransLeft = gnCopySelectSize;
              gnRamTransRef = pcBlock->message.your_ref;
            }
            break;
          case SAVETYPE_CLIPBOARD:
            if (gpcClipboard)
            {
              gpcRamTransPos = gpcClipboard;
              gnRamTransLeft = gnClipboardSize;
              gnRamTransRef = pcBlock->message.your_ref;
            }
            break;
          default:
            gnRamTransRef = -1;
            break;
        }
      }

      if ((gnRamTransRef == pcBlock->message.your_ref) && (gpcRamTransPos))
      {
        // Continue with a previously started RAM transfer
        if (gnRamTransLeft < nSize)
        {
          // This will be the last transfer
          nSendLen = gnRamTransLeft;
        }
        else
        {
          // There will be more to transfer after this
          nSendLen = nSize;
        }

        if (nSendLen > 0)
        {
          xwimp_transfer_block (gnTaskHandle, gpcRamTransPos, nDestHandle,
            pcBlock->message.data.ram_xfer.addr, nSendLen);
        }

        pcBlock->message.size = 28;
        pcBlock->message.your_ref = pcBlock->message.my_ref;
        pcBlock->message.action = message_RAM_TRANSMIT;
        pcBlock->message.data.ram_xfer.size = nSendLen;

        xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
          pcBlock->message.sender);

        gpcRamTransPos += nSendLen;
        gnRamTransLeft -= nSendLen;

        if (nSendLen < nSize)
        {
          gnRamTransRef = -1;
        }
        else
        {
          gnRamTransRef = pcBlock->message.my_ref;
        }
      }
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Response to a RamFetch
void RamTransmit (wimp_block *pcBlock)
{
  char                        *pcLoadedFile;
  int                         nSize;
  int                         nStartLine;
  int                         nEndLine;
  int                         nSuccess = 1;

  if (gnMyRef == pcBlock->message.your_ref)
  {
    pcLoadedFile = pcBlock->message.data.ram_xfer.addr;
    nSize = pcBlock->message.data.ram_xfer.size;

    if (nSize != 0)
    {
      switch (geLoadType)
      {
        case LOADTYPE_EDITTEXT:
          if (gboDelete)
          {
            DeleteSection (gsCaret.nEditNum, & gsSelect);
          }

          InsertText (gsCaret.nEditNum, gsCaret.nPos, gsCaret.nLine,
            pcLoadedFile, nSize);
          ReFormat (gsCaret.nEditNum, gsCaret.nLine, -1,
            & nStartLine, & nEndLine);
          MoveCaretRight (nSize);
          UpdateEditWindow (gsCaret.nEditNum, nStartLine,
            nEndLine);
          EditChanged (gsCaret.nEditNum);
          ClearSelection ();
          gboDelete = FALSE;
          break;
        default:
          break;
      }
    }

    if (nSuccess == 1)
    {
      if (nSize == RAMTRANSMIT_SIZE)
      {
        // The buffer was full, so we must ask for some more

        pcBlock->message.size = 28;
        pcBlock->message.your_ref = pcBlock->message.my_ref;
        pcBlock->message.action = message_RAM_FETCH;
        pcBlock->message.data.ram_xfer.addr = gpcRamTransBuffer;
        pcBlock->message.data.ram_xfer.size = RAMTRANSMIT_SIZE;

        xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
          pcBlock->message.sender);

        gnMyRef = pcBlock->message.my_ref;
      }
      else
      {
        // This was the final transfer
        geLoadType = LOADTYPE_INVALID;
      }
    }
    else
    {
      // There was a problem allocating memory
      ShowWarningTag ("Er42");
      geLoadType = LOADTYPE_INVALID;
    }
  }
}

//////////////////////////////////////////////////////////////////
// Response to a Help message
void Help (wimp_block *pcBlock)
{
  wimp_w                      whWindow;
  wimp_i                      ihIcon;
  char                        szHelpText[236];
  int                         nStrLen;
  char                        szTag[256];
  int                         nWinNum = 0;
  wimp_selection              sSelection;
  int                         nEditSearch;
  bool                        boGeneral;

  whWindow = *(wimp_w*)(pcBlock->message.data.reserved + 12);
  ihIcon = *(wimp_i*)(pcBlock->message.data.reserved + 16);

   // Icon bar
  if (whWindow == wimp_ICON_BAR)
  {
    nWinNum = 1;
  }

  // Warning window
  if (whWindow == gwhWarn)
  {
    nWinNum = 3;
  }

  // Information window
  if (whWindow == gawhMenuWinHandle[0])
  {
    nWinNum = 4;
  }

  // Save window
  if (whWindow == gwhSave)
  {
    nWinNum = 5;
  }

  // Query window
  if (whWindow == gwhQery)
  {
    nWinNum = 6;
  }

  // Check for other windows
  nEditSearch = 0;
  while ((nWinNum == 0)
    && (gawhEditOpen[nEditSearch] != wimp_BACKGROUND))
  {
    if (whWindow == gawhEditOpen[nEditSearch])
    {
      nWinNum = 12;
    }
    if (whWindow == gasEdit[ganEditOpen[nEditSearch]].whHead)
    {
      nWinNum = 13;
    }
    nEditSearch++;
  }

  if (nWinNum)
  {
    // Window
    szHelpText[0] = 0;
    sprintf (szTag, "H%dI%d", nWinNum, ihIcon);
    strncpy (szHelpText, TagCheck (szTag), sizeof(szHelpText));
    nStrLen = strlen(szHelpText);
    if (nStrLen == 0)
    {
      sprintf (szTag, "H%d", nWinNum);
      strncpy (szHelpText, TagCheck (szTag), sizeof(szHelpText));
    }
    szHelpText[sizeof(szHelpText) - 1] = 0;
    nWinNum = -1;
  }
  else
  {
    // Menu
    wimp_get_menu_state (wimp_GIVEN_WINDOW_AND_ICON, & sSelection,
      whWindow, ihIcon);

    boGeneral = FALSE;
    if (sSelection.items[0] != -1)
    {
      // Iconbar menu
      if (gpcMenuCurrent == gpcIconBarMenu)
      {
        nWinNum = 16;
        if ((sSelection.items[0] == 1) && (sSelection.items[1] != -1))
        {
          nWinNum = 21;
          boGeneral = TRUE;
        }
      }

      // New email menu
      if (gpcMenuCurrent == gpcNewMenu)
      {
        nWinNum = 20;
        // Attachments menu
        if (sSelection.items[0] == 1)
        {
          nWinNum = 34;
          boGeneral = TRUE;
        }
      }

      // Local email font menu
      if (gpcMenuCurrent == gpcLocalFontMenu)
      {
        nWinNum = 23;
        boGeneral = TRUE;
      }

      // Remote email font menu
      if (gpcMenuCurrent == gpcRemoteFontMenu)
      {
        nWinNum = 24;
        boGeneral = TRUE;
      }

    }
    if (nWinNum)
    {
      if (boGeneral)
      {
        sprintf (szTag, "H%d", nWinNum);
      }
      else
      {
        if (sSelection.items[1] != -1)
        {
          sprintf (szTag, "H%dI%dI%d", nWinNum, sSelection.items[0],
            sSelection.items[1]);
        }
        else
        {
          sprintf (szTag, "H%dI%d", nWinNum, sSelection.items[0]);
        }
      }

      strncpy (szHelpText, TagCheck (szTag), sizeof(szHelpText));
      nWinNum = -1;
    }
  }

  if (nWinNum == -1)
  {
    pcBlock->message.size = WORDALIGN((24 + strlen(szHelpText)));
    pcBlock->message.your_ref = pcBlock->message.my_ref;
    pcBlock->message.action = 0x503;
    strncpy (pcBlock->message.data.reserved, szHelpText, sizeof(szHelpText));

    xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
      pcBlock->message.sender);
  }
}

//////////////////////////////////////////////////////////////////
// Obtain string from Messages file
char * Tag (char * szTag)
{
  messagetrans_lookup ((messagetrans_control_block*)gpcMessages,
    szTag, gpcTemp, 256, 0, 0, 0, 0, NULL);

  return gpcTemp;
}

//////////////////////////////////////////////////////////////////
// Obtain string from Messages file with an argument
char * TagArg (char * szTag, char * szArg0)
{
  messagetrans_lookup ((messagetrans_control_block*)gpcMessages,
    szTag, gpcTemp, 256, szArg0, 0, 0, 0, NULL);

  return gpcTemp;
}

//////////////////////////////////////////////////////////////////
// Obtain string from Messages file if it exists
char * TagCheck (char * szTag)
{
  osbool                      boMore = FALSE;
  int                         nUsed = 0;
  char                        *szResult;
  os_error                    *psError;

  xmessagetrans_enumerate_tokens ((messagetrans_control_block*)gpcMessages,
    szTag, gpcTemp, 256, 0, & boMore, & nUsed, NULL);

  if (!boMore)
  {
    strcpy (gpcTemp, "");
    szResult = gpcTemp;
  }
  else
  {
    psError = xmessagetrans_lookup ((messagetrans_control_block*)gpcMessages,
      szTag, gpcTemp, 256, NULL, NULL, NULL, NULL, & szResult, & nUsed);

    if (psError)
    {
      strcpy (gpcTemp, "");
      szResult = gpcTemp;
    }
  }

  return szResult;
}

//////////////////////////////////////////////////////////////////
// Load a file into a block of memory
char * LoadFile (char * szFilename, int * pnSize)
{
  char                        *pcMemory;
  int                         nSize;
  os_error                    *psError;
  fileswitch_object_type      eObjectType;

  xosfile_read_stamped_no_path (szFilename, & eObjectType, NULL, NULL,
    & nSize, NULL, NULL);

  if ((eObjectType != fileswitch_NOT_FOUND) && (nSize > 0))
  {
    if (pnSize)
    {
      *pnSize = nSize;
    }
    pcMemory = malloc (nSize);

    if (pcMemory)
    {
      psError = xosfile_load_stamped_no_path (szFilename, pcMemory, NULL,
        NULL, NULL, NULL, NULL);

      if (psError)
      {
        free (pcMemory);
        pcMemory = NULL;
        err (psError);
      }

    }
    else
    {
      ShowWarningTag ("Er20");
    }
  }
  else
  {
    pcMemory = NULL;
  }

  return pcMemory;
}

////////////////////////////////////////////////////////////////////
//// Load a file into a block of flex memory
//int LoadFileFlex (char * szFilename, char * * ppcMemory, int * pnSize)
//{
//  int                         nSize;
//  int                         nSuccess = 0;
//  fileswitch_object_type      eObjectType;
//  os_error                    *psError;
//
//  xosfile_read_stamped_no_path (szFilename, & eObjectType, NULL, NULL,
//    & nSize, NULL, NULL);
//
//  if ((eObjectType != fileswitch_NOT_FOUND) && (nSize > 0))
//  {
//    if (pnSize)
//    {
//      *pnSize = nSize;
//    }
//    nSuccess = flex_alloc ((flex_ptr) ppcMemory, nSize);
//
//    if ((nSuccess == 1) && (*ppcMemory))
//    {
//      psError = xosfile_load_stamped_no_path (szFilename, * ppcMemory, NULL,
//        NULL, NULL, NULL, NULL);
//
//      if (psError)
//      {
//        flex_free ((flex_ptr) ppcMemory);
//        *ppcMemory = NULL;
//        nSuccess = -1;
//        err (psError);
//      }
//    }
//    else
//    {
//      ShowWarningTag ("Er20");
//      nSuccess = 0;
//      *ppcMemory = NULL;
//    }
//  }
//  else
//  {
//    nSuccess = -1;
//    *ppcMemory = NULL;
//  }
//
//  return nSuccess;
//}

//////////////////////////////////////////////////////////////////
// Load a sprite file into a block of memory
osspriteop_area * LoadSprites (char * szFilename)
{
  osspriteop_area             *pcMemory;
  int                         nSize;

  xosfile_read_stamped_no_path (szFilename, NULL, NULL, NULL, & nSize,
    NULL, NULL);

  if (nSize > 0)
  {
    nSize += 4;
    pcMemory = (osspriteop_area*)malloc (nSize);

    if (pcMemory)
    {
      pcMemory->size = nSize;
      pcMemory->sprite_count = 0;
      pcMemory->first = 16;
      pcMemory->used = 16;
      xosspriteop_clear_sprites (osspriteop_USER_AREA, pcMemory);
      err (xosspriteop_load_sprite_file (osspriteop_USER_AREA, pcMemory,
        szFilename));
    }
  }
  else
  {
    pcMemory = NULL;
  }

  return pcMemory;
}

//////////////////////////////////////////////////////////////////
// Display an error from the Messages file with an argument in a warning box
void ShowWarningTagArg (char * szTag, char * szArg0)
{
    xos_bell  ();
    Log ("ERR : ", TagArg(szTag, szArg0));
    SetIconText (TagArg(szTag, szArg0), gwhWarn, 1);
    OpenWindowInitCentre (gwhWarn);
    CloseMenu ();

    xwimp_set_caret_position (gwhWarn, wimp_ICON_WINDOW, 0, 0, -1, -1);
}

//////////////////////////////////////////////////////////////////
// Display an error from the Messages file in a warning box
void ShowWarningTag (char * szTag)
{
    xos_bell  ();
    Log ("ERR : ", Tag(szTag));
    SetIconText (Tag(szTag), gwhWarn, 1);
    OpenWindowInitCentre (gwhWarn);
    CloseMenu ();

    xwimp_set_caret_position (gwhWarn, wimp_ICON_WINDOW, 0, 0, -1, -1);
}

//////////////////////////////////////////////////////////////////
// Display an error in a warning box
void ShowWarning (os_error * sError)
{
    xos_bell  ();
    Log ("ERR : ", sError->errmess);
    SetIconText (sError->errmess, gwhWarn, 1);
    OpenWindowInitCentre (gwhWarn);
    CloseMenu ();

    xwimp_set_caret_position (gwhWarn, wimp_ICON_WINDOW, 0, 0, -1, -1);
}

//////////////////////////////////////////////////////////////////
// Close the warning message box
void CloseWarning (void)
{
  CloseWindowHandle (gwhWarn);
}

//////////////////////////////////////////////////////////////////
// Display a query from the Messages file in a warning box
void ShowQueryTag (char * szTag, char * szContinueTag, char * szCancelTag, QUERYACTION eAction)
{
  xos_bell  ();
  SetIconText (Tag(szTag), gwhQery, 1);
  SetIconText (Tag(szContinueTag), gwhQery, 0);
  SetIconText (Tag(szCancelTag), gwhQery, 5);

  OpenWindowInitCentre (gwhQery);
  CloseMenu ();

  xwimp_set_caret_position (gwhQery, wimp_ICON_WINDOW, 0, 0, -1, -1);

  geQueryAction = eAction;
}

//////////////////////////////////////////////////////////////////
// Display a volatile query from the Messages file in a warning box
void ShowQueryTagVolatile (char * szTag, char * szContinueTag, char * szCancelTag, QUERYACTION eAction)
{
  wimp_window_state           sState;
  int                         nHeight;
  int                         nWidth;
  int                         nEigFactor;
  int                         nSize;
  int                         nXPos;
  int                         nYPos;

  xos_bell  ();
  SetIconText (Tag(szTag), gwhQery, 1);
  SetIconText (Tag(szContinueTag), gwhQery, 0);
  SetIconText (Tag(szCancelTag), gwhQery, 5);

  sState.w = gwhQery;
  xwimp_get_window_state (& sState);

  nWidth = sState.visible.x1 - sState.visible.x0;
  nHeight = sState.visible.y1 - sState.visible.y0;

  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_XEIG_FACTOR,
    & nEigFactor, NULL);
  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_XWIND_LIMIT,
    & nSize, NULL);
  nXPos = ((nSize << nEigFactor) - nWidth) / 2;

  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_YEIG_FACTOR,
    & nEigFactor, NULL);
  xos_read_mode_variable (os_CURRENT_MODE, os_MODEVAR_YWIND_LIMIT,
    & nSize, NULL);
  nYPos = ((nSize << nEigFactor) + nHeight) / 2;

//  OpenWindowInitCentre (gwhQery);
  CloseMenu ();
  xwimp_create_menu ((wimp_menu *)gwhQery, nXPos, nYPos);

  xwimp_set_caret_position (gwhQery, wimp_ICON_WINDOW, 0, 0, -1, -1);

  geQueryAction = eAction;
}


//////////////////////////////////////////////////////////////////
// Close the warning message box
void CloseQuery (void)
{
  CloseWindowHandle (gwhQery);
}


//////////////////////////////////////////////////////////////////
// Initiate a sprite drag from the contents of the given icon
void DragSprite (wimp_w whWindow, wimp_i ihIcon, char * szIconName)
{
  osspriteop_area             *pcSpriteArea;
  int                         nXinc;
  int                         nYinc;
  wimp_icon_state             sIconState;
  wimp_window_info            sWindowInfo;

  if (!gboDrag)
  {
    sIconState.w = whWindow;
    sIconState.i = ihIcon;
    xwimp_get_icon_state (& sIconState);

    if (((sIconState.icon.flags & (wimp_ICON_TEXT
      | wimp_ICON_SPRITE | wimp_ICON_INDIRECTED))
      == (wimp_ICON_SPRITE | wimp_ICON_INDIRECTED)))
    {
      pcSpriteArea = sIconState.icon.data.indirected_sprite.area;
    }
    else
    {
      pcSpriteArea = NULL;
    }

    sWindowInfo.w = whWindow;
    xwimp_get_window_info_header_only (& sWindowInfo);

    if (!pcSpriteArea)
    {
      pcSpriteArea = sWindowInfo.sprite_area;
    }

    nXinc = sWindowInfo.visible.x0 - sWindowInfo.xscroll;
    nYinc = sWindowInfo.visible.y1 - sWindowInfo.yscroll;

    sIconState.icon.extent.x0 += nXinc;
    sIconState.icon.extent.y0 += nYinc;
    sIconState.icon.extent.x1 += nXinc;
    sIconState.icon.extent.y1 += nYinc;

    xdragasprite_start ((dragasprite_HPOS_CENTRE | dragasprite_VPOS_CENTRE
      | dragasprite_BOUND_POINTER | dragasprite_DROP_SHADOW),
      pcSpriteArea, szIconName, & sIconState.icon.extent, NULL);

    gboDrag = TRUE;
  }
}

//////////////////////////////////////////////////////////////////
// Initiate an outline drag from the dimensions of the given icon
void DragBox (wimp_w whWindow, wimp_i ihIcon)
{
  int                         nXinc;
  int                         nYinc;
  wimp_icon_state             sIconState;
  wimp_window_state           sWindowState;
  wimp_drag                   sDragInfo;

  if (!gboDrag)
  {
    sIconState.w = whWindow;
    sIconState.i = ihIcon;
    xwimp_get_icon_state (& sIconState);

    sWindowState.w = whWindow;
    xwimp_get_window_state (& sWindowState);

    nXinc = sWindowState.visible.x0 - sWindowState.xscroll;
    nYinc = sWindowState.visible.y1 - sWindowState.yscroll;

    sDragInfo.w = 0;
    sDragInfo.type = wimp_DRAG_USER_FIXED;
    sDragInfo.initial.x0 = sIconState.icon.extent.x0 + nXinc;
    sDragInfo.initial.y0 = sIconState.icon.extent.y0 + nYinc;
    sDragInfo.initial.x1 = sIconState.icon.extent.x1 + nXinc;
    sDragInfo.initial.y1 = sIconState.icon.extent.y1 + nYinc;
    sDragInfo.bbox.x0 = 0;
    sDragInfo.bbox.y0 = 0;
    sDragInfo.bbox.x1 = 0;
    sDragInfo.bbox.y1 = 0;
    sDragInfo.handle = NULL;
    sDragInfo.draw = NULL;
    sDragInfo.undraw = NULL;
    sDragInfo.redraw = NULL;

    xwimp_drag_box (& sDragInfo);

    gboDrag = TRUE;
  }
}

//////////////////////////////////////////////////////////////////
// Initiate a sprite drag from the given sprite icon name
void DragCentred (int nXPos, int nYPos, char * szIconName)
{
  int              nWidth;
  int              nHeight;
  os_mode          sMode;
  int              nEigFactor;
  os_box           sBox;

  if (!gboDrag)
  {
    xwimpspriteop_read_sprite_info (szIconName, & nWidth, & nHeight,
      NULL, & sMode);

    xos_read_mode_variable (sMode, os_MODEVAR_XEIG_FACTOR, & nEigFactor,
      NULL);
    nWidth = nWidth << nEigFactor;

    xos_read_mode_variable (sMode, os_MODEVAR_YEIG_FACTOR, & nEigFactor,
      NULL);
    nHeight = nHeight << nEigFactor;

    sBox.x0 = nXPos - (nWidth >> 1);
    sBox.y0 = nYPos - (nHeight >> 1);
    sBox.x1 = sBox.x0 + nWidth;
    sBox.y1 = sBox.y0 + nHeight;

    xdragasprite_start ((dragasprite_flags)(0xc5),
      (osspriteop_area*)(1), szIconName, & sBox,
      NULL);

    gboDrag = TRUE;
  }
}

//////////////////////////////////////////////////////////////////
// Initiate a URL link
void OpenURL (char * szURL)
{
  wimp_message                sMessage;

  sMessage.your_ref = 0;
  sMessage.action = message_URL_LAUNCH;

  strcpy ((char *)sMessage.data.reserved, szURL);

  sMessage.size = WORDALIGN(strlen(szURL) + 21);

  xwimp_send_message (wimp_USER_MESSAGE_RECORDED, & sMessage,
    wimp_BROADCAST);

  gnMessageMyRef = sMessage.my_ref;
}

//////////////////////////////////////////////////////////////////
// No URL lancher responded, so we have to run one ourselves
void OpenURLReturned (wimp_block *pcBlock)
{
  char                        szAlias[255];
  char                        szVar[255];
  int                         nUsed = 1;

  if (gnMessageMyRef == pcBlock->message.my_ref)
  {
    if (strncmp (pcBlock->message.data.reserved, "mailto:", 7) == 0)
    {
      strcpy (szAlias, "URLOpen_MailTo");
    }
    else
    {
      strcpy (szAlias, "URLOpen_HTTP");
    }

    sprintf (szVar, "Alias$%s", szAlias);
    xos_read_var_val (szVar, 0, -1, 0, os_VARTYPE_STRING, & nUsed,
      NULL, NULL);

    if (nUsed != 0)
    {
      sprintf (szVar, "%s %s", szAlias, pcBlock->message.data.reserved);
      err (xwimp_start_task (szVar, NULL));
    }
  }
}

//////////////////////////////////////////////////////////////////
// Save box save button clicked
void SaveSave (void)
{
  char                        *szFilename;

  szFilename = GetIconText (gwhSave, 2);

  if (strchr(szFilename, '.'))
  {
    // Save the file
    SaveFile (szFilename);

    // Increment the filename in the icon
    SetIconTextIncrement (szFilename, gwhSave, 2);
  }
  else
  {
    ShowWarningTag ("Er1");
  }
}

#if defined _DEBUG
//////////////////////////////////////////////////////////////////
// Report a debug message
void Report (char * szMessage)
{
  _kernel_swi_regs            sRegs;

  sRegs.r[0] = (int)szMessage;
  _kernel_swi (Report_Text0, & sRegs, & sRegs);
}

//////////////////////////////////////////////////////////////////
// Report a debug message with variable
void ReportVar (char * szFormat, int nVariable)
{
  _kernel_swi_regs            sRegs;
  char                        szReport[255];

  sprintf (szReport, szFormat, nVariable);
  sRegs.r[0] = (int)szReport;
  _kernel_swi (Report_Text0, & sRegs, & sRegs);
}
#endif // _DEBUG

//////////////////////////////////////////////////////////////////
// Save file routine - saves the example file out
void SaveFile (char * szFilename)
{
  os_fw                       fhFile;
  int                         nEmailPos;
  int                         nUnwritten = 0;

  err (xosfile_create_stamped (szFilename, gnSaveFileType, 0));
  err (xosfind_openoutw ((osfind_NO_PATH | osfind_ERROR_IF_DIR), szFilename,
    NULL, & fhFile));

  if (fhFile > 0)
  {
    nEmailPos = 0;
    while ((nEmailPos < gnEmailSaveNum) && (nUnwritten == 0))
    {
      err (xosgbpb_writew (fhFile,
        gasEdit[ganEmailSave[nEmailPos]].szRawEmail,
        gasEdit[ganEmailSave[nEmailPos]].nSize,
        & nUnwritten));
      nEmailPos++;
    }
    osfind_closew (fhFile);
    osfile_set_type (szFilename, gnSaveFileType);
  }
  gnEmailSaveNum = 0;
}

//////////////////////////////////////////////////////////////////
// Respond to a redraw window request
void RedrawWindow (wimp_block *pcBlock)
{
  osbool                boMore;
  int                   nEditNum;
  wimp_w                whWindow;

  xwimp_redraw_window (& pcBlock->redraw, & boMore);
  whWindow = pcBlock->redraw.w;

  // Check if it's an edit window
  nEditNum = 0;
  while ((gawhEditOpen[nEditNum] != whWindow)
    && (gawhEditOpen[nEditNum] != wimp_BACKGROUND))
  {
    nEditNum++;
  }

  if (gawhEditOpen[nEditNum] != wimp_BACKGROUND)
  {
    while (boMore)
    {
      // Redraw the window
      RedrawEdit (& pcBlock->redraw, ganEditOpen[nEditNum]);
      xwimp_get_rectangle (& pcBlock->redraw, & boMore);
    }
  }
  else
  {
    while (boMore)
    {
      // Do nothing - no window found!
      xwimp_get_rectangle (& pcBlock->redraw, & boMore);
    }
  }
}

//////////////////////////////////////////////////////////////////
// Change the selection and update the window
void ChangeSelection (Selection * psNew)
{
  int                   nLineStart;
  int                   nLineEnd;
  osbool                boMore;
  wimp_draw             sRedraw;
  bool                  boPost;
  Selection             sSelectPre;
  Selection             sSelectPost;
  int                   nOffset;
  int                   nLineHide;

  SelectReOrder (psNew);

  if ((psNew->nEmailNum == gsSelect.nEmailNum)
    && (psNew->nEmailNum != EDIT_INVALID))
  {
    // Change selection on same email
    if ((psNew->nLineStart != gsSelect.nLineStart)
      || (psNew->nCharStart != gsSelect.nCharStart)
      || (psNew->nLineEnd != gsSelect.nLineEnd)
      || (psNew->nCharEnd != gsSelect.nCharEnd))
    {
      nLineStart = MIN (gsSelect.nLineStart, psNew->nLineStart);
      nLineEnd = MIN (gsSelect.nLineEnd, psNew->nLineEnd);

      if (gsSelect.nLineStart == -1)
      {
        boPost = FALSE;
        nLineStart = psNew->nLineStart;
        nLineEnd = psNew->nLineEnd;
        sSelectPre = *psNew;
      }
      else
      {
        if (psNew->nLineStart == -1)
        {
          boPost = FALSE;
          nLineStart = gsSelect.nLineStart;
          nLineEnd = gsSelect.nLineEnd;
          sSelectPre = gsSelect;
        }
        else
        {
          boPost = TRUE;
          sSelectPre = gsSelect;
          sSelectPost = gsSelect;

          sSelectPre.nLineStart = gsSelect.nLineStart;
          sSelectPre.nCharStart = gsSelect.nCharStart;
          sSelectPre.nLineEnd = psNew->nLineStart;
          sSelectPre.nCharEnd = psNew->nCharStart;

          sSelectPost.nLineStart = gsSelect.nLineEnd;
          sSelectPost.nCharStart = gsSelect.nCharEnd;
          sSelectPost.nLineEnd = psNew->nLineEnd;
          sSelectPost.nCharEnd = psNew->nCharEnd;

          SelectReOrder (& sSelectPre);
          SelectReOrder (& sSelectPost);
          nLineStart = sSelectPre.nLineStart;
          nLineEnd = sSelectPost.nLineEnd;
        }
      }

      nOffset = gasEdit[psNew->nEmailNum].nTextOffset;
      nLineHide = 0;

      sRedraw.w = gsSelect.whWindow;
      sRedraw.box.x0 = EMAIL_TEXT_X_OFFSET;
      sRedraw.box.y0 = - ((nLineEnd - nLineHide) * gnEmailRowHeight)
        - nOffset - gnEmailTextYOffset - gnEmailRowHeight;
      sRedraw.box.x1 = EMAIL_TEXT_X_OFFSET + gnEmailTextWidth;
      sRedraw.box.y1 = - ((nLineStart - nLineHide) * gnEmailRowHeight)
        - nOffset - gnEmailTextYOffset;

      xwimp_update_window (& sRedraw, & boMore);

      while (boMore)
      {
        InvertSelection (& sSelectPre, & sRedraw);
        if (boPost)
        {
          InvertSelection (& sSelectPost, & sRedraw);
        }
        xwimp_get_rectangle (& sRedraw, & boMore);
      }
      gsSelect = *psNew;
    }
  }
  else
  {
    // Change selection on different email

    // Remove previous selection
    if ((gsSelect.whWindow != wimp_BACKGROUND)
      && (gsSelect.nLineStart != -1))
    {
      nLineStart = gsSelect.nLineStart;
      nLineEnd = gsSelect.nLineEnd;

      nOffset = gasEdit[gsSelect.nEmailNum].nTextOffset;
      nLineHide = 0;

      sRedraw.w = gsSelect.whWindow;
      sRedraw.box.x0 = EMAIL_TEXT_X_OFFSET;
      sRedraw.box.y0 = - ((nLineEnd - nLineHide) * gnEmailRowHeight)
        - nOffset - gnEmailTextYOffset - gnEmailRowHeight;
      sRedraw.box.x1 = EMAIL_TEXT_X_OFFSET + gnEmailTextWidth;
      sRedraw.box.y1 = - ((nLineStart - nLineHide) * gnEmailRowHeight)
        - nOffset - gnEmailTextYOffset;

      xwimp_update_window (& sRedraw, & boMore);

      while (boMore)
      {
        InvertSelection (& gsSelect, & sRedraw);
        xwimp_get_rectangle (& sRedraw, & boMore);
      }
    }

    // Draw new selection
    if ((psNew->whWindow != wimp_BACKGROUND)
      && (psNew->nLineStart != -1))
    {
      nLineStart = psNew->nLineStart;
      nLineEnd = psNew->nLineEnd;

      nOffset = gasEdit[psNew->nEmailNum].nTextOffset;
      nLineHide = 0;

      sRedraw.w = psNew->whWindow;
      sRedraw.box.x0 = EMAIL_TEXT_X_OFFSET;
      sRedraw.box.y0 = - ((nLineEnd - nLineHide) * gnEmailRowHeight)
        - nOffset - gnEmailTextYOffset - gnEmailRowHeight;
      sRedraw.box.x1 = EMAIL_TEXT_X_OFFSET + gnEmailTextWidth;
      sRedraw.box.y1 = - ((nLineStart - nLineHide) * gnEmailRowHeight)
        - nOffset - gnEmailTextYOffset;

      xwimp_update_window (& sRedraw, & boMore);

      while (boMore)
      {
        InvertSelection (psNew, & sRedraw);
        xwimp_get_rectangle (& sRedraw, & boMore);
      }
    }
    gsSelect = *psNew;
  }
}

//////////////////////////////////////////////////////////////////
// Reorder the start and end of a Select structure to be increasing
void SelectReOrder (Selection *psSelection)
{
  int                   nTemp;

  if ((psSelection->nLineStart > psSelection->nLineEnd)
    || ((psSelection->nLineStart == psSelection->nLineEnd)
    && (psSelection->nCharStart > psSelection->nCharEnd)))
  {
    // Switch the start and end positions
    nTemp = psSelection->nLineStart;
    psSelection->nLineStart = psSelection->nLineEnd;
    psSelection->nLineEnd = nTemp;
    nTemp = psSelection->nCharStart;
    psSelection->nCharStart = psSelection->nCharEnd;
    psSelection->nCharEnd = nTemp;
  }
}

//////////////////////////////////////////////////////////////////
// Invert a selected area of text
void InvertSelection (Selection * psSelect, wimp_draw *psRedraw)
{
  int                   nVisMinX = psRedraw->box.x0;
  int                   nVisMaxY = psRedraw->box.y1;
  int                   nRedMinY = psRedraw->clip.y0;
  int                   nRedMaxY = psRedraw->clip.y1;
  int                   nScrollX = psRedraw->xscroll;
  int                   nScrollY = psRedraw->yscroll;

  int                   nLine;
  int                   nStartLine;
  int                   nEndLine;
  int                   nXPos;
  int                   nYPos;
  int                   nXSelStart;
  int                   nXSelEnd;
  char                  *szEmailText;
  int                   *anLine;
  int                   nOffset;
  int                   nLineHide;

  if ((psSelect->nLineStart != psSelect->nLineEnd)
    || (psSelect->nCharStart != psSelect->nCharEnd))
  {
    nOffset = gasEdit[psSelect->nEmailNum].nTextOffset;
    nStartLine = ((-nOffset - gnEmailTextYOffset + nVisMaxY - nScrollY
      - nRedMaxY) / gnEmailRowHeight);

    nEndLine = ((-nOffset - gnEmailTextYOffset + nVisMaxY - nScrollY
      - nRedMinY) / gnEmailRowHeight);

    RANGE (nStartLine, 0, gasEdit[psSelect->nEmailNum].nLineMax);
    RANGE (nEndLine, 0, gasEdit[psSelect->nEmailNum].nLineMax);
    szEmailText = gasEdit[psSelect->nEmailNum].szRawEmail;
    anLine = gasEdit[psSelect->nEmailNum].anLine;
    nLineHide = 0;

    if (nStartLine < psSelect->nLineStart)
    {
      nStartLine = psSelect->nLineStart;
    }
    if (nEndLine > (psSelect->nLineEnd))
    {
      nEndLine = (psSelect->nLineEnd);
    }

    // Draw the whole lot, a line at a time
    nXPos = nVisMinX - nScrollX + EMAIL_TEXT_X_OFFSET;
    for (nLine = nStartLine; nLine <= nEndLine; nLine++)
    {
      nYPos = nVisMaxY - nScrollY
        - ((nLine + 1 - nLineHide) * gnEmailRowHeight)
        - nOffset - gnEmailTextYOffset;

      // Invert if selected
      if ((nLine >= psSelect->nLineStart) && (nLine <= psSelect->nLineEnd))
      {
        if (nLine == psSelect->nLineStart)
        {
          xfont_scan_string (ghEmailFont, szEmailText + anLine[nLine],
            (font_GIVEN_FONT | font_GIVEN_LENGTH),
            MILLIPOINT * gnEmailTextWidth,
            MILLIPOINT * gnEmailRowHeight, NULL, NULL,
              psSelect->nCharStart, NULL, & nXSelStart, NULL, NULL);
          nXSelStart /= MILLIPOINT;
        }
        else
        {
          nXSelStart = 0;
        }
        if (nLine == psSelect->nLineEnd)
        {
          xfont_scan_string (ghEmailFont, szEmailText + anLine[nLine],
            (font_GIVEN_FONT | font_GIVEN_LENGTH),
            MILLIPOINT * gnEmailTextWidth,
            MILLIPOINT * gnEmailRowHeight, NULL, NULL, psSelect->nCharEnd,
            NULL, & nXSelEnd, NULL, NULL);
          nXSelEnd /= MILLIPOINT;
        }
        else
        {
          nXSelEnd = gnEmailTextWidth;
        }
        if (nXSelStart != nXSelEnd)
        {
          xwimp_set_colour (wimp_COLOUR_BLACK);
          xos_plot (os_PLOT_POINT | os_MOVE_TO, nXPos + nXSelStart, nYPos);
          xos_plot (os_PLOT_RECTANGLE | os_PLOT_INVERSE_TO,
            nXPos + nXSelEnd - 1, nYPos + gnEmailRowHeight - 1);
        }
      }
    }
  }
}

//////////////////////////////////////////////////////////////////
// Insert colour codes in to an email line
void InsertEmailColour (char *szFormatted, int * pnMemory, os_colour uCol, os_colour uBack)
{
  szFormatted[*pnMemory + 0] = 19;
  szFormatted[*pnMemory + 1] = (uBack >>  8) & 0xff;
  szFormatted[*pnMemory + 2] = (uBack >> 16) & 0xff;
  szFormatted[*pnMemory + 3] = (uBack >> 24) & 0xff;
  szFormatted[*pnMemory + 4] = (uCol >>  8) & 0xff;
  szFormatted[*pnMemory + 5] = (uCol >> 16) & 0xff;
  szFormatted[*pnMemory + 6] = (uCol >> 24) & 0xff;
  szFormatted[*pnMemory + 7] = 14;
  *pnMemory += 8;
}

//////////////////////////////////////////////////////////////////
// Check the style settings for a particular word
void CheckStyles (char * szWordStart, char * szWordEnd, unsigned int * puStyles)
{
  bool                  boStyleStop;

  boStyleStop = FALSE;
  while ((szWordStart < szWordEnd) && (!boStyleStop))
  {
    if (*szWordStart == *szWordEnd)
    {
      switch (*szWordStart)
      {
        case '*':
          *puStyles |= STYLE_BOLD;
          break;
        case '/':
          *puStyles |= STYLE_ITALIC;
          break;
        case '_':
          *puStyles |= STYLE_UNDERLINE;
          break;
        case '-':
          *puStyles |= STYLE_STRIKE;
          break;
        default:
          boStyleStop = TRUE;
          break;
      }
    }
    else
    {
      boStyleStop = TRUE;
    }
    szWordStart++;
    szWordEnd--;
  }
}

//////////////////////////////////////////////////////////////////
// Initiate a outline drag from the dimensions of the given icon
void DragSelectionBox (int nEmailNum, int nXPos, int nYPos)
{
  int                         nXinc;
  int                         nYinc;
  wimp_window_state           sWindowState;
  wimp_drag                   sDragInfo;
  Selection                   sSelect;
  wimp_auto_scroll_info       sScrollInfo;
  wimp_w                      whWindow;

  if (!gboDrag)
  {
    whWindow = gasEdit[nEmailNum].whWindow;
    FindEditCoords (nEmailNum, nXPos, nYPos, & sSelect.nLineEnd,
      & sSelect.nCharEnd);

    sSelect.nLineStart = gnSelectLineStart;
    sSelect.nCharStart = gnSelectCharStart;
//    sSelect.nLineEnd = gnSelectLineStart;
//    sSelect.nCharEnd = gnSelectCharStart;
    sSelect.whWindow = whWindow;
    sSelect.nEmailNum = nEmailNum;

    ChangeSelection (& sSelect);

    sScrollInfo.w = whWindow;
    sScrollInfo.pause_zone_sizes.x0 = 80;
    sScrollInfo.pause_zone_sizes.y0 = 80;
    sScrollInfo.pause_zone_sizes.x1 = 80;
    sScrollInfo.pause_zone_sizes.y1 = 80;
    sScrollInfo.pause_duration = 0;
    sScrollInfo.state_change = NULL;
    sScrollInfo.handle = NULL;
    xwimp_auto_scroll ((wimp_AUTO_SCROLL_ENABLE_HORIZONTAL
      | wimp_AUTO_SCROLL_ENABLE_VERTICAL), & sScrollInfo, NULL);

    sWindowState.w = whWindow;
    xwimp_get_window_state (& sWindowState);

    nXinc = sWindowState.visible.x0 - sWindowState.xscroll;
    nYinc = sWindowState.visible.y1 - sWindowState.yscroll;

    sDragInfo.w = 0;
    sDragInfo.type = wimp_DRAG_USER_POINT;
    sDragInfo.initial.x0 = nXPos;
    sDragInfo.initial.y0 = nYPos;
    sDragInfo.initial.x1 = nXPos;
    sDragInfo.initial.y1 = nYPos;
    sDragInfo.bbox = sWindowState.visible;
    sDragInfo.handle = NULL;
    sDragInfo.draw = NULL;
    sDragInfo.undraw = NULL;
    sDragInfo.redraw = NULL;

    xwimp_drag_box (& sDragInfo);

    gboDrag = TRUE;
  }
}

//////////////////////////////////////////////////////////////////
// Null Poll code
void NullPoll (wimp_block *pcBlock)
{
  wimp_pointer                sPointer;
  Selection                   sSelect;
  char                        *szWordStart;
  char                        *szWordEnd;
  bool                        boLink;
  int                         nSendAmount;

  pcBlock = pcBlock;

  xwimp_get_pointer_info (& sPointer);

  if (gboDrag)
  {
    switch (geSaveType)
    {
      case SAVETYPE_SELECTCHOOSE:
        FindEditCoords (gsSelect.nEmailNum, sPointer.pos.x, sPointer.pos.y,
          & sSelect.nLineEnd, & sSelect.nCharEnd);
        if (gboAllowMouseCaret)
        {
          SetCaretPos (gsSelect.nEmailNum, sSelect.nLineEnd,
            sSelect.nCharEnd);
        }
        gsCaret.nCharPref = gsCaret.nChar;
        sSelect.nLineStart = gnSelectLineStart;
        sSelect.nCharStart = gnSelectCharStart;
        sSelect.whWindow = gsSelect.whWindow;
        sSelect.nEmailNum = gsSelect.nEmailNum;
        ChangeSelection (& sSelect);
        break;
      default:
        break;
    }
  }
  else
  {
    if (gnPointerEmail != EDIT_INVALID)
    {
      FindWordAtCoords (gnPointerEmail,
        sPointer.pos.x, sPointer.pos.y, & szWordStart, & szWordEnd);
      boLink = FALSE;
      if (szWordStart)
      {
        boLink = WordIsLink (szWordStart, szWordEnd);
      }
      if ((!boLink) && gboLinkPointer)
      {
        // Remove hand pointer
        gboLinkPointer = FALSE;
        xwimpspriteop_set_pointer_shape ("ptr_default", 97, 0, 0, NULL,
          NULL);
      }
      if (boLink && (!gboLinkPointer))
      {
        // Invoke hand pointer
        gboLinkPointer = TRUE;
        xwimpspriteop_set_pointer_shape ("ptr_point", 97, 0, 0, NULL,
          NULL);
      }
    }
  }

  // Send any data if we have to
  if (gnDataSend >= 0)
  {
    // Send some more data
    nSendAmount = ganDataSize - gnDataSend;
    if (nSendAmount > gnSendSize)
    {
      nSendAmount = gnSendSize;
    }

    if (nSendAmount > 0)
    {
      // Send the data
      LinkSend (0, gpcDataSend + gnDataSend, nSendAmount);
      gnDataSend += nSendAmount;
    }
    else
    {
      // We've sent everything, so we're done with this file
      flex_free ((flex_ptr)(& gpcDataSend));
      gpcDataSend = NULL;
      ganDataSize = 0;
      gnDataSend = -1;
      LinkClose(0);
      SetNullPollActive (NULLPOLL_ACTIVE_SLOW);
    }
  }
}

//////////////////////////////////////////////////////////////////
// Find position of pount relative to a selecton (before/within/after)
RELPOS PointRelativeSelection (int nEmailNum, int nXPos, int nYPos, Selection * psSelect, int * pnLine, int * pnChar)
{
  int                         nLine;
  int                         nChar;
  RELPOS                      eReturn;

  FindEditCoords (nEmailNum, nXPos, nYPos, & nLine, & nChar);

  if ((nEmailNum != psSelect->nEmailNum)
    || (psSelect->nLineStart == -1))
  {
    eReturn = RELPOS_OTHEREMAIL;
  }
  else
  {
    eReturn = RELPOS_INVALID;
    if ((nLine < psSelect->nLineStart)
      || ((nLine == psSelect->nLineStart) && (nChar < psSelect->nCharStart)))
    {
      eReturn = RELPOS_BEFORE;
    }
    if ((nLine > psSelect->nLineEnd)
      || ((nLine == psSelect->nLineEnd) && (nChar > psSelect->nCharEnd)))
    {
      eReturn = RELPOS_AFTER;
    }
    if (eReturn == RELPOS_INVALID)
    {
      eReturn = RELPOS_WITHINSTART;
      if ((nLine - psSelect->nLineStart) > (psSelect->nLineEnd - nLine))
      {
        eReturn = RELPOS_WITHINEND;
      }
      else
      {
        if (((nLine - psSelect->nLineStart) == (psSelect->nLineEnd - nLine))
          && ((nChar - psSelect->nCharStart) > (psSelect->nCharEnd - nChar)))
        {
          eReturn = RELPOS_WITHINEND;
        }
      }
    }
  }

  if (pnLine)
  {
    *pnLine = nLine;
  }
  if (pnChar)
  {
    *pnChar = nChar;
  }
  return eReturn;
}

//////////////////////////////////////////////////////////////////
// Create and open a new edit window
int CreateEditWindow (void)
{
  wimp_WINDOW_INFO(20)        sWindow;
  wimp_window_info            *psWindow = (wimp_window_info*)&sWindow;

  int                         nCount;
  int                         nEditNum;
  int                         nAllocSuccess;
  char                        szNumber[4];

  nEditNum = 0;
  while ((gasEdit[nEditNum].whWindow != wimp_BACKGROUND)
    && (nEditNum < EDIT_MAX))
  {
    nEditNum++;
  }

  if (nEditNum < EDIT_MAX)
  {
    nAllocSuccess = flex_alloc ((flex_ptr)(& gasEdit[nEditNum].szRawEmail),
      (EDIT_CHARS_MAX * sizeof(char)));

    if ((nAllocSuccess != 0) && (gasEdit[nEditNum].szRawEmail != NULL))
    {
      gasEdit[nEditNum].nEmailMem = (EDIT_CHARS_MAX * sizeof(char));

      for (nCount = 0; nCount < EDIT_CHARS_MAX; nCount++)
      {
        gasEdit[nEditNum].szRawEmail[nCount] = 0;
      }
      // Create space for formatting info
      nAllocSuccess = flex_alloc ((flex_ptr)(& gasEdit[nEditNum].anLine),
        (EDIT_LINE_MAX * sizeof(int)));

      if ((nAllocSuccess != 0) && (gasEdit[nEditNum].anLine != NULL))
      {
        gasEdit[nEditNum].nLineMem = (EDIT_LINE_MAX * sizeof(int));

        for (nCount = 0; nCount < EDIT_LINE_MAX; nCount++)
        {
          gasEdit[nEditNum].anLine[nCount] = 0;
        }

        sWindow.w = gwhEdit;
        err (xwimp_get_window_info (psWindow));

        gnOpenOffset = (OPENWIN_OFFSET_MAX + gnOpenOffset + OPENWIN_OFFSET)
          % OPENWIN_OFFSET_MAX;

        sWindow.visible.x0 -= gnOpenOffset;
        sWindow.visible.y0 += gnOpenOffset;
        sWindow.visible.x1 -= gnOpenOffset;
        sWindow.visible.y1 += gnOpenOffset;

        sWindow.extent.x0 = 0;
        sWindow.extent.x1 = gnEmailTextWidth + (2 * EMAIL_TEXT_X_OFFSET);

        sWindow.title_data.indirected_text.text =
          gasEdit[nEditNum].szWinTitle;
        sWindow.title_data.indirected_text.size = EDIT_TITLE_LEN;
        strncpy (gasEdit[nEditNum].szWinTitle, Tag("EditTitle"),
          EDIT_TITLE_LEN);

        err (xwimp_create_window ((wimp_window *)((char *)(psWindow)
          + sizeof(wimp_w)), & gasEdit[nEditNum].whWindow));

        gasEdit[nEditNum].szRawEmail[0] = 0x0a;
        gasEdit[nEditNum].nSize = 1;
        gasEdit[nEditNum].anLine[0] = 0;
        gasEdit[nEditNum].anLine[1] = 1;

        sWindow.w = gwhEdHe;
        err (xwimp_get_window_info (psWindow));

        // Set the header window extent
        gasEdit[nEditNum].boButtons = FALSE;

        sWindow.extent.x0 = 0;
        sWindow.extent.x1 = gnEmailTextWidth + (2 * EMAIL_TEXT_X_OFFSET);
        if (gasEdit[nEditNum].boButtons)
        {
          gasEdit[nEditNum].nTextOffset = EDIT_TEXT_Y_OFFSET_MAX;
          sWindow.extent.y0 = - EDIT_HEAD_EXTENT_MAX;
          sWindow.icons[9].flags &= ~wimp_ICON_SHADED;
          sWindow.icons[12].flags &= ~wimp_ICON_SHADED;
          sWindow.icons[14].flags |= wimp_ICON_SELECTED;
        }
        else
        {
          gasEdit[nEditNum].nTextOffset = EDIT_TEXT_Y_OFFSET_MIN;
          sWindow.extent.y0 = - EDIT_HEAD_EXTENT_MIN;
          sWindow.icons[9].flags |= wimp_ICON_SHADED;
          sWindow.icons[12].flags |= wimp_ICON_SHADED;
          sWindow.icons[14].flags &= ~wimp_ICON_SELECTED;
        }
        sWindow.extent.y1 = 0;

        err (xwimp_create_window ((wimp_window *)((char *)(psWindow)
          + sizeof(wimp_w)), & gasEdit[nEditNum].whHead));

        gasEdit[nEditNum].nLineMax = 1;
        gasEdit[nEditNum].nFormattedTo = 0;
        gasEdit[nEditNum].boInsert = FALSE;
        gasEdit[nEditNum].boLineChange = FALSE;
        gasEdit[nEditNum].boChanged = FALSE;

        // Reformat everything
    //    ReFormat (nEditNum, -1, -1, NULL, NULL);

        if (gasEdit[nEditNum].boButtons)
        {
          OpenWindowInitPaneNew (gasEdit[nEditNum].whWindow,
            gasEdit[nEditNum].whHead, PANE_TOP);
        }
        else
        {
          OpenWindowInitNew (gasEdit[nEditNum].whWindow);
        }

        // Add this to the list of email windows
        nCount = 0;
        while ((gawhEditOpen[nCount] != wimp_BACKGROUND)
          && (nCount < EDIT_MAX))
        {
          nCount++;
        }
        if (nCount < EDIT_MAX)
        {
          gawhEditOpen[nCount] = gasEdit[nEditNum].whWindow;
          ganEditOpen[nCount] = nEditNum;
        }
        SetCaretPos (ganEditOpen[nCount], 0, 0);
        gsCaret.nCharPref = gsCaret.nChar;
      }
      else
      {
        ShowWarningTag ("Er4");

        flex_free ((flex_ptr)(& gasEdit[nEditNum].szRawEmail));
        gasEdit[nEditNum].szRawEmail = NULL;
        nEditNum = -1;
      }
    }
    else
    {
      ShowWarningTag ("Er4");
      nEditNum = -1;
    }
  }
  else
  {
    sprintf(szNumber, "%d", EDIT_MAX);
    ShowWarningTagArg ("Er5", szNumber);
    nEditNum = -1;
  }

  return nEditNum;
}

//////////////////////////////////////////////////////////////////
// Destroy an Edit window and all of its associated data
void DestroyEditWindow (int nEditNum)
{
  int                         nEditSearch;
  bool                        boWinFound;
  int                         nCount;

  gasEdit[nEditNum].nSize = -1;

  if (gsCaret.whWindow == gasEdit[nEditNum].whWindow)
  {
    gboCaretLost = TRUE;
    ClearCaret ();
  }

  err (xwimp_delete_window (gasEdit[nEditNum].whWindow));
  gasEdit[nEditNum].whWindow = wimp_BACKGROUND;

  err (xwimp_delete_window (gasEdit[nEditNum].whHead));
  gasEdit[nEditNum].whHead = wimp_BACKGROUND;

  if (gasEdit[nEditNum].szRawEmail != NULL)
  {
    flex_free ((flex_ptr)(& gasEdit[nEditNum].szRawEmail));
    gasEdit[nEditNum].szRawEmail = NULL;
  }
  if (gasEdit[nEditNum].szFormEmail != NULL)
  {
    flex_free ((flex_ptr)(& gasEdit[nEditNum].szFormEmail));
    gasEdit[nEditNum].szFormEmail = NULL;
  }
  if (gasEdit[nEditNum].anLine != NULL)
  {
    flex_free ((flex_ptr)(& gasEdit[nEditNum].anLine));
    gasEdit[nEditNum].anLine = NULL;
  }

  gasEdit[nEditNum].boInsert = FALSE;
  gasEdit[nEditNum].boLineChange = FALSE;
  gasEdit[nEditNum].boButtons = FALSE;
  gasEdit[nEditNum].nTextOffset = EDIT_TEXT_Y_OFFSET_MIN;
  gasEdit[nEditNum].boChanged = FALSE;

  // Find and remove its reference
  boWinFound = FALSE;
  nEditSearch = 0;
  while ((!boWinFound) && (gawhEditOpen[nEditSearch] != wimp_BACKGROUND))
  {
    if (nEditNum == ganEditOpen[nEditSearch])
    {
      nCount = nEditSearch;
      while (gawhEditOpen[nCount] != wimp_BACKGROUND)
      {
        gawhEditOpen[nCount] = gawhEditOpen[nCount + 1];
        ganEditOpen[nCount] = ganEditOpen[nCount + 1];
        nCount++;
      }
      boWinFound = TRUE;
    }
    nEditSearch++;
  }
}

//////////////////////////////////////////////////////////////////
// Reformat to a given point
void ReFormat (int nEditNum, int nChangedLine, int nLineEnd, int * psStartLine, int * psEndLine)
{
  int                   nLineStart = 0;
  int                   nReturn = 0;
  int                   nLineEndFull;

  if (nLineEnd == -1)
  {
    // Reformat everything
    nLineEndFull = (gasEdit[nEditNum].nLineMem / sizeof(int));
  }
  else
  {
    nLineEndFull = nLineEnd;
  }

  if ((nChangedLine != -1)
    || (nLineEndFull > gasEdit[nEditNum].nFormattedTo))
  {
    if ((gasEdit[nEditNum].nFormattedTo < nChangedLine)
      || (nChangedLine == -1))
    {
      nLineStart = gasEdit[nEditNum].nFormattedTo;
    }
    else
    {
      nLineStart = nChangedLine - 1;
      if (nLineStart < 0)
      {
        nLineStart = 0;
      }
    }

    if (nLineStart < nLineEndFull)
    {
      nReturn = FormatLines (nEditNum, nLineStart, nLineEnd);
    }
    else
    {
      if (nChangedLine < gasEdit[nEditNum].nFormattedTo)
      {
        gasEdit[nEditNum].nFormattedTo = nChangedLine;
      }
    }
  }

  if (psStartLine)
  {
    *psStartLine = nLineStart;
  }
  if (psEndLine)
  {
    {
      *psEndLine = nLineStart + nReturn;
    }
  }
}

//////////////////////////////////////////////////////////////////
// Reformat between points
//int FormatLines (int nEditNum, int nLineStart, int nLineEnd)
//{
//  char                  *szRawEmail;
//  char                  *szLineEnd;
//  char                  *szCurrentPos;
//  char                  *szEditEnd;
//  bool                  boFormatted;
//  int                   nLine;
//  font_scan_block       sScanBlock;
//  int                   nPos;
//  int                   nReturn;
//  int                   nAllocSuccess;
//  int                   nLineLength;
//  int                   nCurrentPos;
//  int                   nEditEnd;
//  int                   nLineEndShift;
//
//  sScanBlock.space.x = 0;
//  sScanBlock.space.y = 0;
//  sScanBlock.letter.x = 0;
//  sScanBlock.letter.y = 0;
//  sScanBlock.split_char = 32;
//
//  if (nLineEnd == -1)
//  {
//    // Reformat everything
//    nLineEnd = (gasEdit[nEditNum].nLineMem / sizeof(int));
//  }
//
//  // Draw the whole lot, a line at a time
//  szRawEmail = gasEdit[nEditNum].szRawEmail;
//  szCurrentPos = szRawEmail + gasEdit[nEditNum].anLine[nLineStart];
//  nLine = nLineStart;
//  szEditEnd = szRawEmail + gasEdit[nEditNum].nSize;
//  boFormatted = FALSE;
//  while ((szCurrentPos < szEditEnd) && (!boFormatted) && (nLine < nLineEnd))
//  {
//    // Find out how much to plot
//    xfont_scan_string (ghEmailFont, szCurrentPos, (font_GIVEN_FONT
//      | font_GIVEN_BLOCK),
//      MILLIPOINT * gnEmailTextWidth,
//      MILLIPOINT * gnEmailRowHeight, & sScanBlock,
//      NULL, 0, & szLineEnd, NULL, NULL, NULL);
//
//    if (szLineEnd == szCurrentPos)
//    {
//      // Could not split at a space
//      xfont_scan_string (ghEmailFont, szCurrentPos, (font_GIVEN_FONT),
//        MILLIPOINT * gnEmailTextWidth,
//        MILLIPOINT * gnEmailRowHeight, NULL,
//        NULL, 0, & szLineEnd, NULL, NULL, NULL);
//    }
//
//    nPos = szCurrentPos - szRawEmail;
//
//    szCurrentPos = szLineEnd;
//    nLineLength = szLineEnd - szRawEmail;
//
//    nLine++;
//    if ((szCurrentPos[0] == 0x0a) || (szCurrentPos[0] == 0x0d)
//      || (szCurrentPos[0] == 0x20))
//    {
//      szCurrentPos++;
//    }
//
//    if (nLine >= (gasEdit[nEditNum].nLineMem / (int)sizeof(int)))
//    {
//      nCurrentPos = szCurrentPos - szRawEmail;
//      nEditEnd = szEditEnd - szCurrentPos;
//      nLineEndShift = szLineEnd - szCurrentPos;
//      nAllocSuccess = ExtendLineMem (nEditNum);
//      szRawEmail = gasEdit[nEditNum].szRawEmail;
//      szCurrentPos = szRawEmail + nCurrentPos;
//      szEditEnd = szCurrentPos + nEditEnd;
//      szLineEnd = szCurrentPos + nLineEndShift;
//
//      if (nAllocSuccess == 0)
//      {
//        REPORT ("Reformatting error when extending line memory");
//        boFormatted = TRUE;
//      }
//    }
//
//    if (((szCurrentPos - szRawEmail) == gasEdit[nEditNum].anLine[nLine])
//      && (nLine < gasEdit[nEditNum].nFormattedTo)
//      && (nLine > (nLineStart + 1)))
//    {
//      boFormatted = TRUE;
//    }
//
//    gasEdit[nEditNum].anLine[nLine] = szCurrentPos - szRawEmail;
//
//    if ((gsCaret.nEditNum == nEditNum) && (gsCaret.nPos >= nPos)
//      && (gsCaret.nPos < nLineLength))
//    {
//      SetCaretPos (nEditNum, nLine - 1, gsCaret.nPos - nPos);
//    }
//  }
//
//  if (nLine > gasEdit[nEditNum].nFormattedTo)
//  {
//    gasEdit[nEditNum].nFormattedTo = nLine;
//  }
//
//  nReturn = nLine - nLineStart;
//
//  if ((szCurrentPos >= szEditEnd) || (nLine >= gasEdit[nEditNum].nLineMax))
//  {
//    gasEdit[nEditNum].nLineMax = nLine;
//    gasEdit[nEditNum].anLine[nLine] = gasEdit[nEditNum].nSize;
//    // Set the window size
//    SetEditHeight (nEditNum, nLine);
//  }
//
//  return nReturn;
//
//}

//////////////////////////////////////////////////////////////////
// Reformat between points
int FormatLines (int nEditNum, int nLineStart, int nLineEnd)
{
  char                  *szLineEnd;
  char                  *szCurrentPos;
  bool                  boFormatted;
  int                   nLine;
  font_scan_block       sScanBlock;
  int                   nPos;
  int                   nReturn;
  int                   nAllocSuccess;
  int                   nLineLength;
  int                   nCurrentPos;
  int                   nEditEnd;
  char                  cLineEndChar;
  int                   nLineEndFull;

  sScanBlock.space.x = 0;
  sScanBlock.space.y = 0;
  sScanBlock.letter.x = 0;
  sScanBlock.letter.y = 0;
  sScanBlock.split_char = 32;

  if (nLineEnd == -1)
  {
    // Reformat everything
    nLineEndFull = (gasEdit[nEditNum].nLineMem / (int)sizeof(int));
  }
  else
  {
    nLineEndFull = nLineEnd;
  }

  // Draw the whole lot, a line at a time
  nCurrentPos = gasEdit[nEditNum].anLine[nLineStart];
  nLine = nLineStart;
  nEditEnd = gasEdit[nEditNum].nSize;
  boFormatted = FALSE;
  while ((nCurrentPos < nEditEnd) && (!boFormatted)
    && (nLine < nLineEndFull))
  {
    // Find out how much to plot
    szCurrentPos = gasEdit[nEditNum].szRawEmail + nCurrentPos;
    xfont_scan_string (ghEmailFont, szCurrentPos,
      (font_GIVEN_FONT | font_GIVEN_BLOCK),
      MILLIPOINT * gnEmailTextWidth,
      MILLIPOINT * gnEmailRowHeight, & sScanBlock,
      NULL, 0, & szLineEnd, NULL, NULL, NULL);

    if (szLineEnd == szCurrentPos)
    {
      // Could not split at a space
      xfont_scan_string (ghEmailFont, szCurrentPos, (font_GIVEN_FONT),
        MILLIPOINT * gnEmailTextWidth,
        MILLIPOINT * gnEmailRowHeight, NULL,
        NULL, 0, & szLineEnd, NULL, NULL, NULL);
    }

    nPos = nCurrentPos;
    nCurrentPos = szLineEnd - gasEdit[nEditNum].szRawEmail;
    nLineLength = nCurrentPos;

    nLine++;
    cLineEndChar = gasEdit[nEditNum].szRawEmail[nCurrentPos];
    if ((cLineEndChar == 0x0a) || (cLineEndChar == 0x0d)
      || (cLineEndChar == 0x20))
    {
      nCurrentPos++;
    }

    if (nLine >= (gasEdit[nEditNum].nLineMem / (int)sizeof(int)))
    {
      nAllocSuccess = ExtendLineMem (nEditNum);
      if (nLineEnd == -1)
      {
        // Reformat everything
        nLineEndFull = (gasEdit[nEditNum].nLineMem / (int)sizeof(int));
      }

      if (nAllocSuccess == 0)
      {
        REPORT ("Reformatting error when extending line memory");
        boFormatted = TRUE;
      }
    }

    if ((nLine < gasEdit[nEditNum].nFormattedTo)
      && (nLine > (nLineStart + 1))
      && (nCurrentPos == gasEdit[nEditNum].anLine[nLine]))
    {
      boFormatted = TRUE;
    }

    gasEdit[nEditNum].anLine[nLine] = nCurrentPos;

    if ((gsCaret.nEditNum == nEditNum) && (gsCaret.nPos >= nPos)
      && (gsCaret.nPos < nLineLength))
    {
      SetCaretPos (nEditNum, nLine - 1, gsCaret.nPos - nPos);
    }
  }

  if (nLine > gasEdit[nEditNum].nFormattedTo)
  {
    gasEdit[nEditNum].nFormattedTo = nLine;
  }

  nReturn = nLine - nLineStart;

  if ((nCurrentPos >= nEditEnd) || (nLine >= gasEdit[nEditNum].nLineMax))
  {
    gasEdit[nEditNum].nLineMax = nLine;
    gasEdit[nEditNum].anLine[nLine] = gasEdit[nEditNum].nSize;
    // Set the window size
    SetEditHeight (nEditNum, nLine);
  }

  return nReturn;
}

//////////////////////////////////////////////////////////////////
// Redraw an edit window
void RedrawEdit (wimp_draw *psRedraw, int nEditNum)
{
  int                   nVisMinX = psRedraw->box.x0;
//  int                   nVisMinY = psRedraw->box.y0;
//  int                   nVisMaxX = psRedraw->box.x1;
  int                   nVisMaxY = psRedraw->box.y1;

  int                   nRedMinX = psRedraw->clip.x0;
  int                   nRedMinY = psRedraw->clip.y0;
  int                   nRedMaxX = psRedraw->clip.x1;
  int                   nRedMaxY = psRedraw->clip.y1;

  int                   nScrollX = psRedraw->xscroll;
  int                   nScrollY = psRedraw->yscroll;

  int                   nLine;
  char                  *szCurrentPos;
  char                  *szEditEnd;
  int                   nXPos;
  int                   nYPos;
//  os_colour_number      nColour;
//  os_colour_number      nCaretColour;
  int                   nLineLen;
  int                   nLineStart;
  int                   nLineEnd;
  int                   nXSelStart;
  int                   nXSelEnd;
  char                  szPlotLine[1024*2];
  int                   nIndent;
  int                   nIndentCheck;
  os_colour             uCol;
  int                   nIndentPos;
  int                   nPlotLen;

  if (nEditNum != EDIT_INVALID)
  {
    xfont_set_font (ghEmailFont);

    nLineStart = ((-gasEdit[nEditNum].nTextOffset - gnEmailTextYOffset
      + nVisMaxY - nScrollY - nRedMaxY) / gnEmailRowHeight);
    nLineEnd = ((-gasEdit[nEditNum].nTextOffset - gnEmailTextYOffset
      + nVisMaxY - nScrollY - nRedMinY) / gnEmailRowHeight) + 1;

    RANGE (nLineStart, 0, gasEdit[nEditNum].nLineMax);
    RANGE (nLineEnd, 0, gasEdit[nEditNum].nLineMax);

    ReFormat (nEditNum, -1, nLineEnd, NULL, NULL);

    // Draw the whole lot, a line at a time
    nXPos = nVisMinX - nScrollX + EMAIL_TEXT_X_OFFSET;
    nLine = nLineStart;
    szCurrentPos = gasEdit[nEditNum].szRawEmail;
    szEditEnd = gasEdit[nEditNum].szRawEmail + gasEdit[nEditNum].nSize;

      // Fill in the background
    xwimp_set_colour (WIMP_BG_COLOUR);
    xos_plot (os_PLOT_POINT | os_MOVE_TO, nRedMinX, nRedMinY);
    xos_plot (os_PLOT_RECTANGLE | os_PLOT_TO, nRedMaxX, nRedMaxY);

    while ((nLine < nLineEnd) && (szCurrentPos < szEditEnd))
    {
      nYPos = nVisMaxY - nScrollY - ((nLine + 1) * gnEmailRowHeight)
        - gasEdit[nEditNum].nTextOffset;

      if (gasEdit[nEditNum].anLine[nLine + 1] != 0)
      {
        uCol = os_COLOUR_BLACK;
        nLineLen = gasEdit[nEditNum].anLine[nLine + 1]
          - gasEdit[nEditNum].anLine[nLine];

        RANGE (nLineLen, 0, (int)sizeof(szPlotLine));

        // Check indent
        nIndent = 0;
        szCurrentPos = gasEdit[nEditNum].szRawEmail
          + gasEdit[nEditNum].anLine[nLine];
        nIndentCheck = 0;
        while ((strchr (" >", szCurrentPos[nIndentCheck]))
          && (nIndentCheck < nLineLen))
        {
          if (strchr (">", szCurrentPos[nIndentCheck]))
          {
            nIndent++;
          }
          nIndentCheck++;
        }
        nIndentPos = 0;
        if (nIndent > 0)
        {
          // Add additional colouring bytes
          uCol = gauIndCol[nIndent % INDENT_CYCLE];
          InsertEmailColour (szPlotLine, & nIndentPos, uCol,
            OS_BG_COLOUR);
        }

        // Check for colourings and fonts
        nPlotLen = nIndentPos + CopyFormatted (gasEdit[nEditNum].szRawEmail
          + gasEdit[nEditNum].anLine[nLine], szPlotLine + nIndentPos,
          nLineLen, uCol, OS_BG_COLOUR);

        xwimp_set_font_colours (WIMP_BG_COLOUR, wimp_COLOUR_BLACK);

        xfont_paint (ghEmailFont, szPlotLine,
          (font_OS_UNITS | font_GIVEN_LENGTH),
          nXPos, nYPos, NULL, NULL, nPlotLen);

        if (gboEmoticons)
        {
          PlotSmileys(ghEmailFont, szPlotLine, nXPos,
            nYPos - gnEmailTextYOffset, nPlotLen);
        }
        szCurrentPos += nLineLen;
      }

      nYPos -= gnEmailTextYOffset;

      // Invert if selected
      if ((nEditNum == gsSelect.nEmailNum)
        && (nLine >= gsSelect.nLineStart) && (nLine <= gsSelect.nLineEnd)
        && (gsSelect.nLineStart != -1))
      {
        if (nLine == gsSelect.nLineStart)
        {
          xfont_scan_string (ghEmailFont, gasEdit[nEditNum].szRawEmail
            + gasEdit[nEditNum].anLine[nLine],
            (font_GIVEN_FONT | font_GIVEN_LENGTH),
            MILLIPOINT * gnEmailTextWidth,
            MILLIPOINT * gnEmailRowHeight, NULL, NULL, gsSelect.nCharStart,
            NULL, & nXSelStart, NULL, NULL);
          nXSelStart /= MILLIPOINT;
        }
        else
        {
          nXSelStart = 0;
        }
        if (nLine == gsSelect.nLineEnd)
        {
          xfont_scan_string (ghEmailFont, gasEdit[nEditNum].szRawEmail
            + gasEdit[nEditNum].anLine[nLine],
            (font_GIVEN_FONT | font_GIVEN_LENGTH),
            MILLIPOINT * gnEmailTextWidth,
            MILLIPOINT * gnEmailRowHeight, NULL, NULL, gsSelect.nCharEnd,
            NULL, & nXSelEnd, NULL, NULL);
          nXSelEnd /= MILLIPOINT;
        }
        else
        {
          nXSelEnd = gnEmailTextWidth;
        }
        if (nXSelStart != nXSelEnd)
        {
          xwimp_set_colour (wimp_COLOUR_BLACK);
          xos_plot (os_PLOT_POINT | os_MOVE_TO, nXPos + nXSelStart, nYPos);
          xos_plot (os_PLOT_RECTANGLE | os_PLOT_INVERSE_TO,
            nXPos + nXSelEnd - 1, nYPos + gnEmailRowHeight - 1);
        }
      }

      szCurrentPos++;
      nLine++;
    }

    // Draw the caret
    if (gsCaret.nEditNum == nEditNum)
    {
      DrawCaret (nVisMinX - nScrollX + gsCaret.nXPos,
        nVisMaxY - nScrollY + gsCaret.nYPos);
    }
  }
}

//////////////////////////////////////////////////////////////////
// Copy a string and add formatting
int CopyFormatted (char * szFrom, char * szTo, int nLen, os_colour uCol, os_colour uBack)
{
  int                   nCopied;
  int                   nWordStart;
  int                   nWordEnd;
  font_f                hFont = ghEmailFont;
  int                   nMemory;
  unsigned int          uStyles;
  int                   nSmiley;

  nMemory = nLen;
  uStyles = 0;
  nCopied = 0;
  while (nCopied < nLen)
  {
    if (((nCopied == 0) || (szFrom[nCopied - 1] == ' '))
      && (szFrom[nCopied] != ' '))
    {
      // Establish the start and end of the current word
      nWordStart = nCopied;
      nWordEnd = nCopied;
      do
      {
        nWordEnd++;
      } while ((nWordEnd <= nLen) && (szFrom[nWordEnd] > ' '));
      nWordEnd--;

      // Now do some checks on the word

      // Styles
      if (strchr ("*/_-", szFrom[nCopied]))
      {
        CheckStyles (szFrom + nWordStart, szFrom + nWordEnd, & uStyles);

        if (uStyles & STYLE_BOLD)
        {
          hFont = ghEmailBFont;
        }
        if (uStyles & STYLE_ITALIC)
        {
          hFont = ghEmailIFont;
        }
        if ((uStyles & STYLE_BOLD) && (uStyles & STYLE_ITALIC))
        {
          hFont = ghEmailBIFont;
        }

        if (uStyles & (STYLE_BOLD | STYLE_ITALIC))
        {
          if (szTo)
          {
            szTo[nCopied] = 26;
            szTo[nCopied + 1] = hFont;
            szTo += 2;
          }
          nMemory += 2;
        }

        if (uStyles & STYLE_UNDERLINE)
        {
          if (szTo)
          {
            szTo[nCopied] = 25;
            szTo[nCopied + 1] = 220;
            szTo[nCopied + 2] = 25;
            szTo += 3;
          }
          nMemory += 3;
        }
        else
        {
          if (uStyles & STYLE_STRIKE)
          {
            if (szTo)
            {
              szTo[nCopied] = 25;
              szTo[nCopied + 1] = 80;
              szTo[nCopied + 2] = 25;
              szTo += 3;
            }
            nMemory += 3;
          }
        }
      }
      // Check for other styles
      if (WordIsLink (szFrom + nWordStart, szFrom + nWordEnd))
      {
        uStyles |= STYLE_LINK;
      }

      if (uStyles & STYLE_LINK)
      {
        if (szTo)
        {
          szTo[nCopied + 0] = 19;
          szTo[nCopied + 1] = (uBack >>  8) & 0xff;
          szTo[nCopied + 2] = (uBack >> 16) & 0xff;
          szTo[nCopied + 3] = (uBack >> 24) & 0xff;
          szTo[nCopied + 4] = (os_COLOUR_LIGHT_GREEN >>  8) & 0xff;
          szTo[nCopied + 5] = (os_COLOUR_LIGHT_GREEN >> 16) & 0xff;
          szTo[nCopied + 6] = (os_COLOUR_LIGHT_GREEN >> 24) & 0xff;
          szTo[nCopied + 7] = 14;
          szTo += 8;
        }
        nMemory += 8;
      }

      if (gboEmoticons)
      {
        nSmiley = WordIsSmiley (szFrom + nWordStart, szFrom + nWordEnd);
        if (nSmiley != 0)
        {
          uStyles |= STYLE_HIDDEN;

          if (szTo)
          {
            szTo[nCopied + 0] = 19;
            szTo[nCopied + 1] = (uBack >>  8) & 0xff;
            szTo[nCopied + 2] = (uBack >> 16) & 0xff;
            szTo[nCopied + 3] = (uBack >> 24) & 0xff;
            szTo[nCopied + 4] = (uBack >>  8) & 0xff;
            szTo[nCopied + 5] = (uBack >> 16) & 0xff;
            szTo[nCopied + 6] = (uBack >> 24) & 0xff;
            szTo[nCopied + 7] = 14;
            szTo[nCopied + 8] = 21;
            szTo[nCopied + 9] = 255;
            szTo[nCopied + 10] = nSmiley;
            szTo += 11;
          }
          nMemory += 11;
        }
      }
    }

    // Copy the character across
    if (szTo)
    {
      szTo[nCopied] = szFrom[nCopied];
    }
    nCopied++;


    // Reset the styles after a word
    if ((nCopied == nLen) || (szFrom[nCopied] == ' '))
    {
      // End of word, we may need to reset the styles
      if (uStyles & (STYLE_BOLD | STYLE_ITALIC))
      {
        if (szTo)
        {
          szTo[nCopied] = 26;
          szTo[nCopied + 1] = ghEmailFont;
          szTo += 2;
        }
        nMemory += 2;

        uStyles &= ~(STYLE_BOLD | STYLE_ITALIC);
      }

      if (uStyles & (STYLE_UNDERLINE | STYLE_STRIKE))
      {
        if (szTo)
        {
          szTo[nCopied] = 25;
          szTo[nCopied + 1] = 0;
          szTo[nCopied + 2] = 0;
          szTo += 3;
        }
        nMemory += 3;
        uStyles &= ~(STYLE_UNDERLINE | STYLE_STRIKE);
      }
      if (uStyles & (STYLE_LINK | STYLE_HIDDEN))
      {
        if (szTo)
        {
          szTo[nCopied + 0] = 19;
          szTo[nCopied + 1] = (uBack >>  8) & 0xff;
          szTo[nCopied + 2] = (uBack >> 16) & 0xff;
          szTo[nCopied + 3] = (uBack >> 24) & 0xff;
          szTo[nCopied + 4] = (uCol >>  8) & 0xff;
          szTo[nCopied + 5] = (uCol >> 16) & 0xff;
          szTo[nCopied + 6] = (uCol >> 24) & 0xff;
          szTo[nCopied + 7] = 14;
          szTo += 8;
        }
        nMemory += 8;
        uStyles &= ~(STYLE_LINK | STYLE_HIDDEN);
      }
    }
  }

  return nMemory;
}

//////////////////////////////////////////////////////////////////
// Plot any smileys on screen
void PlotSmileys(font_f hFont, char *szPlotLine, int nXPos, int nYPos, int nPlotLen)
{
  char                        *szSmiley;
  int                         nSmileyXOffset;
  int                         nSmileyNum;

  nSmileyXOffset = nSmileyXOffset;
  hFont = hFont;
  nXPos = nXPos;
  nYPos = nYPos;

  nPlotLen--;
  szSmiley = strnzchr (szPlotLine, 21, nPlotLen - 1);
  while (szSmiley)
  {
    if (szSmiley[1] == 255)
    {
      nSmileyNum = (int)szSmiley[2] - 1;

      xfont_scan_string (hFont, szPlotLine,
        (font_GIVEN_FONT | font_GIVEN_LENGTH),
        MILLIPOINT * gnEmailTextWidth,
        MILLIPOINT * gnEmailRowHeight, NULL, NULL, szSmiley - szPlotLine,
        NULL, & nSmileyXOffset, NULL, NULL);
      nSmileyXOffset /= MILLIPOINT;

      osspriteop_put_sprite_scaled (osspriteop_PTR,
        gpcEmoticons,
//        (osspriteop_id)((char*)gpcEmoticons + gasSmileys[0].nOffset),
        (osspriteop_id)((char*)gpcEmoticons
        + gasSmileys[nSmileyNum].nOffset),
        nXPos + nSmileyXOffset, nYPos,
        osspriteop_USE_MASK, 0, gpsTransTable);

        szSmiley += 2;
    }
    szSmiley = strnzchr (szSmiley + 1, 21,
      nPlotLen + szPlotLine - szSmiley - 1);
  }
}

//////////////////////////////////////////////////////////////////
// Populate the Smiley structure from the Emoticons file
void PopulateSmileys (void)
{
  int                         nSpriteNum;
  osspriteop_header           *psHeader;
  int                         nHeaderOffset;
  int                         nCopy;
  int                         nCopyTo;
  char                        szName[SMILEY_NAME_LEN];
  int                         nNameLen;

  gnSmileyNum = gpcEmoticons->sprite_count;
  if (gnSmileyNum > SMILEY_MAX)
  {
    gnSmileyNum = SMILEY_MAX;
  }

  if (gnSmileyNum > 0)
  {
    nHeaderOffset = gpcEmoticons->first;
    psHeader = ((osspriteop_header*)
      ((char*)gpcEmoticons + (gpcEmoticons->first)));

    for (nSpriteNum = 0; nSpriteNum < gnSmileyNum; nSpriteNum++)
    {
      strncpy (szName, (((osspriteop_header*)
        ((char*)gpcEmoticons + nHeaderOffset))->name), SMILEY_NAME_LEN);
      szName[SMILEY_NAME_LEN - 1] = 0;
      nNameLen = strlen(szName);

      gasSmileys[nSpriteNum].nOffset = nHeaderOffset;

      nCopyTo = 0;
      for (nCopy = 0; nCopy <= nNameLen; nCopy++)
      {
        if (szName[nCopy] == 0xa0)
        {
          if ((nCopy + 1) <= nNameLen)
          {
            nCopy++;
            gasSmileys[nSpriteNum].szName[nCopyTo] = szName[nCopy] - 32;
          }
        }
        else
        {
          gasSmileys[nSpriteNum].szName[nCopyTo] = szName[nCopy];
        }
        nCopyTo++;
      }
      gasSmileys[nSpriteNum].szName[nCopyTo - 1] = 0;

      gasSmileys[nSpriteNum].nNameLength = nCopyTo - 1;

      nHeaderOffset += (((osspriteop_header*)
        ((char*)gpcEmoticons + nHeaderOffset))->size);
    }
  }
}

//////////////////////////////////////////////////////////////////
// Returns the line and char positions of a point in an edit window
void FindEditCoords (int nEditNum, int nXPos, int nYPos, int * pnLine, int * pnChar)
{
  wimp_window_state           sWindowState;
  int                         nXWin;
  int                         nYWin;
  int                         nLine;
  char                        *szChar;
  char                        *szLineStart;

  sWindowState.w = gasEdit[nEditNum].whWindow;
  xwimp_get_window_state (& sWindowState);

  nXWin = nXPos - sWindowState.visible.x0 + sWindowState.xscroll
    - EMAIL_TEXT_X_OFFSET;
  nYWin = nYPos - sWindowState.visible.y1 + sWindowState.yscroll;

  nLine = (nYWin + gasEdit[nEditNum].nTextOffset + gnEmailTextYOffset)
    / (-gnEmailRowHeight);

  if (nLine >= gasEdit[nEditNum].nLineMax)
  {
    nLine = gasEdit[nEditNum].nLineMax - 1;
  }
  if (nLine < 0)
  {
    nLine = 0;
  }

  if (pnLine)
  {
    *pnLine = nLine;
  }

  if (pnChar)
  {
    szLineStart = gasEdit[nEditNum].szRawEmail
      + gasEdit[nEditNum].anLine[nLine];
    xfont_scan_string (ghEmailFont,
      szLineStart,
      (font_GIVEN_FONT | font_RETURN_CARET_POS),
      MILLIPOINT * nXWin,
      MILLIPOINT * gnEmailRowHeight / 2, NULL, NULL, 0,
      & szChar, NULL, NULL, NULL);

    if (szChar)
    {
      *pnChar = szChar - szLineStart;
    }
  }
}

//////////////////////////////////////////////////////////////////
// Set caret position
void SetCaretPos (int nEditNum, int nLine, int nChar)
{
  char                        *szLineStart;
  int                         nXWin;
  int                         nYWin;
  char                        cChar;

  nYWin = ((nLine + 1) * (-gnEmailRowHeight)) - gasEdit[nEditNum].nTextOffset
    - gnEmailTextYOffset;

  szLineStart = gasEdit[nEditNum].szRawEmail
    + gasEdit[nEditNum].anLine[nLine];

  if (nChar > (gasEdit[nEditNum].anLine[nLine + 1]
    - gasEdit[nEditNum].anLine[nLine]))
  {
    nChar = (gasEdit[nEditNum].anLine[nLine + 1]
    - gasEdit[nEditNum].anLine[nLine]);
    cChar = gasEdit[nEditNum].szRawEmail[gasEdit[nEditNum].anLine[nLine]
      + nChar - 1];
    if (cChar == ' ')
    {
      nChar--;
    }
  }

  cChar = gasEdit[nEditNum].szRawEmail[gasEdit[nEditNum].anLine[nLine]
    + nChar - 1];
  if ((cChar == 0x0a) || (cChar == 0x0d))
  {
    nChar--;
  }

  if (nChar < 0)
  {
    nChar = 0;
  }

  xfont_scan_string (ghEmailFont, szLineStart,
    (font_GIVEN_FONT | font_GIVEN_LENGTH),
    MILLIPOINT * gnEmailTextWidth,
    MILLIPOINT * gnEmailRowHeight, NULL, NULL, nChar,
    NULL, & nXWin, NULL, NULL);
  nXWin /= MILLIPOINT;
  nXWin += EMAIL_TEXT_X_OFFSET;

//  if ((gboCaretLost)
//    || (gsCaret.whWindow != gasEdit[nEditNum].whWindow)
//    || (gsCaret.nEditNum != nEditNum)
//    || (gsCaret.nXPos != nXWin)
//    || (gsCaret.nYPos != nYWin))
//  {
//    xwimp_set_caret_position (gasEdit[nEditNum].whWindow, -1, nXWin, nYWin,
//      gnEmailRowHeight, 0);
//    gboCaretLost = FALSE;
//  }

  // Remove the curent caret
  RedrawCaret ();

  gsCaret.whWindow = gasEdit[nEditNum].whWindow;
  gsCaret.nEditNum = nEditNum;
  gsCaret.nLine = nLine;
  gsCaret.nChar = nChar;
  gsCaret.nPos = gasEdit[nEditNum].anLine[nLine] + nChar;
  gsCaret.nXPos = nXWin;
  gsCaret.nYPos = nYWin;

  // Draw the new caret
  RedrawCaret ();

  ScrollWindowToCaret ();
}

//////////////////////////////////////////////////////////////////
// Redraws the caret
void RedrawCaret (void)
{
  wimp_draw                   sRedraw;
  bool                        boMore;
  wimp_window_state           sWindowState;

  if (gsCaret.whWindow != wimp_BACKGROUND)
  {
    sWindowState.w = gsCaret.whWindow;
    xwimp_get_window_state (& sWindowState);

    sRedraw.w = gsCaret.whWindow;
    sRedraw.box.x0 = gsCaret.nXPos;
    sRedraw.box.y0 = gsCaret.nYPos;
    sRedraw.box.x1 = gsCaret.nXPos + CARET_WIDTH + 2;
    sRedraw.box.y1 = gsCaret.nYPos+ CARET_HEIGHT + 2;

    xwimp_update_window (& sRedraw, & boMore);
    while (boMore)
    {
      DrawCaret (sRedraw.box.x0 - sRedraw.xscroll + gsCaret.nXPos,
        sRedraw.box.y1 - sRedraw.yscroll + gsCaret.nYPos);

      xwimp_get_rectangle (& sRedraw, & boMore);
    }
  }
}

//////////////////////////////////////////////////////////////////
// Draw the caret at the given position
void DrawCaret (int nXPos, int nYPos)
{
  xcolourtrans_set_gcol (CARET_COLOUR, 0, os_ACTION_EXCLUSIVE_DISJOIN,
    NULL, NULL);
  xos_plot (os_PLOT_POINT | os_MOVE_TO, nXPos, nYPos);
  xos_plot (os_PLOT_RECTANGLE | os_PLOT_TO,
    nXPos + CARET_WIDTH, nYPos + CARET_HEIGHT);
}

//////////////////////////////////////////////////////////////////
// Insert a character into an edit email
void InsertChar (int nEditNum, int nPos, int nLine, char cChar)
{
  char                        *szStart;
  char                        *szEnd;
  char                        *szChar;
  int                         nAllocSuccess;

  if (gasEdit[nEditNum].boInsert)
  {
    gasEdit[nEditNum].szRawEmail[nPos] = cChar;
  }
  else
  {
    if ((gasEdit[nEditNum].nSize + 1) >= gasEdit[nEditNum].nEmailMem)
    {
      nAllocSuccess = ExtendEmailMem (nEditNum);
    }

    if (gasEdit[nEditNum].nSize < gasEdit[nEditNum].nEmailMem)
    {
      // Shift everything after the character up
      szStart = gasEdit[nEditNum].szRawEmail + nPos;
      if ((cChar == 0x0a) || (cChar == 0x0d))
      {
        if (gasEdit[nEditNum].nLineMax >=
          (gasEdit[nEditNum].nLineMem / (int)sizeof(int)))
        {
          nAllocSuccess = ExtendLineMem (nEditNum);
          szStart = gasEdit[nEditNum].szRawEmail + nPos;
        }

        if ((gasEdit[nEditNum].nLineMax <
          ((gasEdit[nEditNum].nLineMem / (int)sizeof(int)) - 1)))
        {
          ShiftLines (nEditNum, nLine, 1, 1);
          szEnd = gasEdit[nEditNum].szRawEmail + gasEdit[nEditNum].nSize;
          for (szChar = szEnd; szChar >= szStart; szChar--)
          {
            szChar[1] = szChar[0];
          }
          szStart[0] = cChar;
          gasEdit[nEditNum].nSize++;
        }
      }
      else
      {
        ShiftLines (nEditNum, nLine, 0, 1);
        szEnd = gasEdit[nEditNum].szRawEmail + gasEdit[nEditNum].nSize;
        for (szChar = szEnd; szChar >= szStart; szChar--)
        {
          szChar[1] = szChar[0];
        }
        szStart[0] = cChar;
        gasEdit[nEditNum].nSize++;
      }
    }
  }
}

//////////////////////////////////////////////////////////////////
// Remove a character from an edit email
void DeleteChar (int nEditNum, int nPos, int nLine)
{
  char                        *szStart;
  char                        *szEnd;
  char                        *szChar;

  // Shift everything down
  if ((nPos < (gasEdit[nEditNum].nSize - 1)) && (nPos >= 0))
  {
    szStart = gasEdit[nEditNum].szRawEmail + nPos;
    if ((szStart[0] == 0x0a) || (szStart[0] == 0x0d))
    {
      ShiftLines (nEditNum, nLine, -1, -1);
    }
    else
    {
      ShiftLines (nEditNum, nLine, 0, -1);
    }

    szEnd = gasEdit[nEditNum].szRawEmail + gasEdit[nEditNum].nSize - 1;
    for (szChar = szStart; szChar < szEnd; szChar++)
    {
      szChar[0] = szChar[1];
    }

    gasEdit[nEditNum].nSize--;
  }
}

//////////////////////////////////////////////////////////////////
// Remove a section of characters from an edit email
bool DeleteSection (int nEditNum, Selection * psSelect)
{
  char                        *szStart;
  char                        *szEnd;
  char                        *szChar;
  int                         nShift;
  bool                        boReturn = FALSE;

  if (gboAllowMouseCaret
    && (nEditNum == psSelect->nEmailNum)
    && (psSelect->nLineStart !=-1)
    && ((psSelect->nLineStart != psSelect->nLineEnd)
    || (psSelect->nCharStart != psSelect->nCharEnd)))
  {
    SelectReOrder (psSelect);

    // Shift everything down
    szEnd = gasEdit[nEditNum].szRawEmail + gasEdit[nEditNum].nSize;

    szStart = gasEdit[nEditNum].szRawEmail
      + gasEdit[nEditNum].anLine[psSelect->nLineEnd]
      + psSelect->nCharEnd;

    if (szStart >= (szEnd - 1))
    {
      szStart = szEnd - 1;
    }

    // nShift is *negative*
    nShift = gasEdit[nEditNum].szRawEmail
      + gasEdit[nEditNum].anLine[psSelect->nLineStart]
      + psSelect->nCharStart - szStart;

    if (nShift < 0)
    {
      for (szChar = szStart; szChar < szEnd; szChar++)
      {
        szChar[nShift] = szChar[0];
      }

      gasEdit[nEditNum].nSize += nShift;

      // Preliminary reformatting
      ShiftLines (nEditNum, psSelect->nLineStart,
        psSelect->nLineStart - psSelect->nLineEnd, nShift);
    }

    SetCaretPos (nEditNum, psSelect->nLineStart, psSelect->nCharStart);
    gsCaret.nCharPref = gsCaret.nChar;

    ClearSelection ();

    boReturn = TRUE;
  }
  return boReturn;
}

//////////////////////////////////////////////////////////////////
// Update an edit window display
void UpdateEditWindow (int nEditNum, int nStartLine, int nEndLine)
{
  wimp_window_state           sWindowState;
  int                         nTopLine;
  int                         nBaseLine;
  wimp_draw                   sRedraw;
  bool                        boMore;

  sWindowState.w = gasEdit[nEditNum].whWindow;
  xwimp_get_window_state (& sWindowState);

  nTopLine = (sWindowState.yscroll + gasEdit[nEditNum].nTextOffset
    + gnEmailTextYOffset) / (-gnEmailRowHeight) - 1;

  nBaseLine = (sWindowState.yscroll - (sWindowState.visible.y1
    - sWindowState.visible.y0) + gasEdit[nEditNum].nTextOffset
    + gnEmailTextYOffset) / (-gnEmailRowHeight) + 1;

  if (nTopLine > nStartLine)
  {
    nStartLine = nTopLine;
  }
  if ((nBaseLine < nEndLine) || gasEdit[nEditNum].boLineChange)
  {
    nEndLine = nBaseLine;
    gasEdit[nEditNum].boLineChange = FALSE;
  }

  sRedraw.w = gasEdit[nEditNum].whWindow;
  sRedraw.box.x0 = EMAIL_TEXT_X_OFFSET;
  sRedraw.box.y0 = - (nEndLine * gnEmailRowHeight)
    - gasEdit[nEditNum].nTextOffset - gnEmailTextYOffset;
  sRedraw.box.x1 = EMAIL_TEXT_X_OFFSET + gnEmailTextWidth;
  sRedraw.box.y1 = - (nStartLine * gnEmailRowHeight)
    - gasEdit[nEditNum].nTextOffset - gnEmailTextYOffset;

  xwimp_update_window (& sRedraw, & boMore);

  while (boMore)
  {
    RedrawEdit ( & sRedraw, nEditNum);

    xwimp_get_rectangle (& sRedraw, & boMore);
  }
}

//////////////////////////////////////////////////////////////////
// Move the caret right the given number of characters
void MoveCaretRight (int nRight)
{
  int                         nPos;
  int                         nLine;
  int                         nChar;

  nPos = gsCaret.nPos + nRight;
  nLine = gsCaret.nLine;

  while ((nPos >= gasEdit[gsCaret.nEditNum].anLine[nLine])
    && (nLine < gasEdit[gsCaret.nEditNum].nLineMax))
  {
    nLine++;
  }

  nLine--;
  if (nLine < 0)
  {
    nLine = 0;
  }
  nChar = nPos - gasEdit[gsCaret.nEditNum].anLine[nLine];
  SetCaretPos (gsCaret.nEditNum, nLine, nChar);
  gsCaret.nCharPref = gsCaret.nChar;
}

//////////////////////////////////////////////////////////////////
// Move the caret left the given number of characters
void MoveCaretLeft (int nLeft)
{
  int                         nPos;
  int                         nLine;
  int                         nChar;

  nPos = gsCaret.nPos - nLeft;
  nLine = gsCaret.nLine;

  while ((nPos < gasEdit[gsCaret.nEditNum].anLine[nLine])
    && (nLine > 0))
  {
    nLine--;
  }

  if (nLine < 0)
  {
    nLine = 0;
  }
  nChar = nPos - gasEdit[gsCaret.nEditNum].anLine[nLine];
  SetCaretPos (gsCaret.nEditNum, nLine, nChar);
  gsCaret.nCharPref = gsCaret.nChar;
}

//////////////////////////////////////////////////////////////////
// Move the caret up the given number of rows
void MoveCaretUp (int nUp)
{
  int                         nLine;
  int                         nChar;

  nLine = gsCaret.nLine - nUp;
  nChar = gsCaret.nCharPref;

  RANGE (nLine, 0, gasEdit[gsCaret.nEditNum].nLineMax - 1);

  SetCaretPos (gsCaret.nEditNum, nLine, nChar);
}

//////////////////////////////////////////////////////////////////
// Move the caret down the given number of rows
void MoveCaretDown (int nDown)
{
  int                         nLine;
  int                         nChar;

  nLine = gsCaret.nLine + nDown;
  nChar = gsCaret.nCharPref;

  RANGE (nLine, 0, gasEdit[gsCaret.nEditNum].nLineMax - 1);

  SetCaretPos (gsCaret.nEditNum, nLine, nChar);
}

//////////////////////////////////////////////////////////////////
// Update the selection if the caret moves with shift held down
void ShiftSelection (Caret * psCaretPre, Caret * psCaret)
{
  Selection                   sSelect;

  if ((gsSelect.nEmailNum == psCaretPre->nEditNum)
    && (gsSelect.nLineEnd == psCaretPre->nLine)
    && (gsSelect.nCharEnd == psCaretPre->nChar))
  {
    sSelect = gsSelect;
    sSelect.nLineEnd = psCaret->nLine;
    sSelect.nCharEnd = psCaret->nChar;
    ChangeSelection (& sSelect);
  }
  else
  {
    if ((gsSelect.nEmailNum == psCaretPre->nEditNum)
      && (gsSelect.nLineStart == psCaretPre->nLine)
      && (gsSelect.nCharStart == psCaretPre->nChar))
    {
      sSelect = gsSelect;
      sSelect.nLineStart = psCaret->nLine;
      sSelect.nCharStart = psCaret->nChar;
      ChangeSelection (& sSelect);
    }
    else
    {
      sSelect.nEmailNum = psCaretPre->nEditNum;
      sSelect.whWindow = psCaretPre->whWindow;
      sSelect.nLineStart = psCaretPre->nLine;
      sSelect.nCharStart = psCaretPre->nChar;
      sSelect.nLineEnd = psCaret->nLine;
      sSelect.nCharEnd = psCaret->nChar;
      ChangeSelection (& sSelect);
    }
  }
}

//////////////////////////////////////////////////////////////////
// Move right to the next occurence of one of the szSearch characters
void SearchCaretRight (char * szSearch)
{
  int                         nPos;
  int                         nSize;
  char                        *szRawEmail;

  szRawEmail = gasEdit[gsCaret.nEditNum].szRawEmail;
  nPos = gsCaret.nPos + 1;
  nSize = gasEdit[gsCaret.nEditNum].nSize;

  while ((nPos < nSize) && (strchr(szSearch, szRawEmail[nPos]) == NULL))
  {
    nPos++;
  }

  MoveCaretRight (nPos - gsCaret.nPos);
}

//////////////////////////////////////////////////////////////////
// Move right to the next occurence of one of the szSearch characters
void SearchCaretLeft (char * szSearch)
{
  int                         nPos;
  char                        *szRawEmail;

  szRawEmail = gasEdit[gsCaret.nEditNum].szRawEmail;
  nPos = gsCaret.nPos - 2;

  while ((nPos > 0) && (strchr(szSearch, szRawEmail[nPos]) == NULL))
  {
    nPos--;
  }

  if (nPos > 0)
  {
    MoveCaretLeft (gsCaret.nPos - nPos - 1);
  }
}

//////////////////////////////////////////////////////////////////
// Insert a string into an edit email
void InsertString (int nEditNum, int nPos, int nLine, char * szString)
{
  char                        *szStart;
  char                        *szEnd;
  char                        *szChar;
  int                         nCount;
  int                         nLength;
  int                         nLinesAdded;
  int                         nAllocSuccess;

  nLength = strlen (szString);

  if (gasEdit[nEditNum].boInsert)
  {
    memcpy (gasEdit[nEditNum].szRawEmail + nPos, szString, nLength);
  }
  else
  {
    if ((gasEdit[nEditNum].nSize + nLength) >= gasEdit[nEditNum].nEmailMem)
    {
      nAllocSuccess = ExtendEmailMemTo (nEditNum,
        gasEdit[nEditNum].nSize + nLength);
    }

    if ((gasEdit[nEditNum].nSize + nLength) < gasEdit[nEditNum].nEmailMem)
    {
      nLinesAdded = 0;
      for (nCount = 0; nCount < nLength; nCount++)
      {
        if ((szString[nCount] == 0x0a) || (szString[nCount] == 0x0d))
        {
          nLinesAdded++;
        }
      }

      if ((gasEdit[nEditNum].nLineMax + nLinesAdded)
        >= (gasEdit[nEditNum].nLineMem / (int)sizeof(int)))
      {
        nAllocSuccess = ExtendLineMemTo (nEditNum,
          (gasEdit[nEditNum].nLineMax + nLinesAdded) * sizeof(int));
      }

      if ((gasEdit[nEditNum].nLineMax + nLinesAdded)
        < (gasEdit[nEditNum].nLineMem / (int)sizeof(int)))
      {
        // Shift everything after the string up
        szStart = gasEdit[nEditNum].szRawEmail + nPos;
        szEnd = gasEdit[nEditNum].szRawEmail + gasEdit[nEditNum].nSize;
        for (szChar = szEnd; szChar >= szStart; szChar--)
        {
          szChar[nLength] = szChar[0];
        }

        memcpy (szStart, szString, nLength);
        gasEdit[nEditNum].nSize += nLength;

        ShiftLines (nEditNum, nLine, nLinesAdded, nLength);
      }
    }
  }
}

//////////////////////////////////////////////////////////////////
// Insert a chunk of flex memory into an edit email
void InsertTextFlex (int nEditNum, int nPos, int nLine, char * * pszString, int nLength)
{
  char                        *szStart;
  char                        *szEnd;
  char                        *szChar;
  int                         nCount;
  int                         nLinesAdded;
  int                         nAllocSuccess;

  if (gasEdit[nEditNum].boInsert)
  {
    if ((nPos + nLength) >= gasEdit[nEditNum].nEmailMem)
    {
      nAllocSuccess = ExtendEmailMemTo (nEditNum, (nPos + nLength));
    }

    if ((nPos + nLength) < gasEdit[nEditNum].nEmailMem)
    {
      szStart = gasEdit[nEditNum].szRawEmail + nPos;
      memcpy (szStart, * pszString, nLength);

      // Sanitize the copied text
      for (nCount = 0; nCount < nLength; nCount++)
      {
        if ((szStart[nCount] < 0x20) && (szStart[nCount] != 0x0a)
          && (szStart[nCount] != 0x0d))
        {
          szStart[nCount] = 0x20;
        }
      }
    }
  }
  else
  {
    if ((gasEdit[nEditNum].nSize + nLength) >= gasEdit[nEditNum].nEmailMem)
    {
      nAllocSuccess = ExtendEmailMemTo (nEditNum,
        (gasEdit[nEditNum].nSize + nLength));
    }

    if ((gasEdit[nEditNum].nSize + nLength) < gasEdit[nEditNum].nEmailMem)
    {
      nLinesAdded = 0;
      for (nCount = 0; nCount < nLength; nCount++)
      {
        if (((* pszString)[nCount] == 0x0a)
          || ((* pszString)[nCount] == 0x0d))
        {
          nLinesAdded++;
        }
      }

      if ((gasEdit[nEditNum].nLineMax + nLinesAdded)
        >= (gasEdit[nEditNum].nLineMem / (int)sizeof(int)))
      {
        nAllocSuccess = ExtendLineMemTo (nEditNum,
          ((gasEdit[nEditNum].nLineMax + nLinesAdded) * sizeof(int)));
      }

      if ((gasEdit[nEditNum].nLineMax + nLinesAdded)
        < (gasEdit[nEditNum].nLineMem / (int)sizeof(int)))
      {
        // Shift everything after the string up
        szStart = gasEdit[nEditNum].szRawEmail + nPos;
        szEnd = gasEdit[nEditNum].szRawEmail + gasEdit[nEditNum].nSize;
        for (szChar = szEnd; szChar >= szStart; szChar--)
        {
          szChar[nLength] = szChar[0];
        }

        memcpy (szStart, * pszString, nLength);
        gasEdit[nEditNum].nSize += nLength;

        // Sanitize the copied text
        for (nCount = 0; nCount < nLength; nCount++)
        {
          if ((szStart[nCount] < 0x20) && (szStart[nCount] != 0x0a)
            && (szStart[nCount] != 0x0d))
          {
            szStart[nCount] = 0x20;
          }
        }

        ShiftLines (nEditNum, nLine, nLinesAdded, nLength);
      }
    }
  }
}

//////////////////////////////////////////////////////////////////
// Insert a chunk of memory into an edit email
void InsertText (int nEditNum, int nPos, int nLine, char * szString, int nLength)
{
  char                        *szStart;
  char                        *szEnd;
  char                        *szChar;
  int                         nCount;
  int                         nLinesAdded;
  int                         nAllocSuccess;

  if (gasEdit[nEditNum].boInsert)
  {
    if ((nPos + nLength) >= gasEdit[nEditNum].nEmailMem)
    {
      nAllocSuccess = ExtendEmailMemTo (nEditNum, (nPos + nLength));
    }

    if ((nPos + nLength) < gasEdit[nEditNum].nEmailMem)
    {
      szStart = gasEdit[nEditNum].szRawEmail + nPos;
      memcpy (szStart, szString, nLength);

      // Sanitize the copied text
      for (nCount = 0; nCount < nLength; nCount++)
      {
        if ((szStart[nCount] < 0x20) && (szStart[nCount] != 0x0a)
          && (szStart[nCount] != 0x0d))
        {
          szStart[nCount] = 0x20;
        }
      }
    }
  }
  else
  {
    if ((gasEdit[nEditNum].nSize + nLength) >= gasEdit[nEditNum].nEmailMem)
    {
      nAllocSuccess = ExtendEmailMemTo (nEditNum,
        (gasEdit[nEditNum].nSize + nLength));
    }

    if ((gasEdit[nEditNum].nSize + nLength) < gasEdit[nEditNum].nEmailMem)
    {
      nLinesAdded = 0;
      for (nCount = 0; nCount < nLength; nCount++)
      {
        if ((szString[nCount] == 0x0a) || (szString[nCount] == 0x0d))
        {
          nLinesAdded++;
        }
      }

      if ((gasEdit[nEditNum].nLineMax + nLinesAdded)
        >= (gasEdit[nEditNum].nLineMem / (int)sizeof(int)))
      {
        nAllocSuccess = ExtendLineMemTo (nEditNum,
          ((gasEdit[nEditNum].nLineMax + nLinesAdded) * sizeof(int)));
      }

      if ((gasEdit[nEditNum].nLineMax + nLinesAdded)
        < (gasEdit[nEditNum].nLineMem / (int)sizeof(int)))
      {
        // Shift everything after the string up
        szStart = gasEdit[nEditNum].szRawEmail + nPos;
        szEnd = gasEdit[nEditNum].szRawEmail + gasEdit[nEditNum].nSize;
        for (szChar = szEnd; szChar >= szStart; szChar--)
        {
          szChar[nLength] = szChar[0];
        }

        memcpy (szStart, szString, nLength);
        gasEdit[nEditNum].nSize += nLength;

        // Sanitize the copied text
        for (nCount = 0; nCount < nLength; nCount++)
        {
          if ((szStart[nCount] < 0x20) && (szStart[nCount] != 0x0a)
            && (szStart[nCount] != 0x0d))
          {
            szStart[nCount] = 0x20;
          }
        }

        ShiftLines (nEditNum, nLine, nLinesAdded, nLength);
      }
    }
  }
}

//////////////////////////////////////////////////////////////////
// Do a rough reformatting of the line starts
void ShiftLines (int nEditNum, int nLineStart, int nLine, int nChar)
{
  int                         nCount;
  int                         nEnd;


  if (nLine != 0)
  {
    gasEdit[nEditNum].boLineChange = TRUE;
  }

  if (nChar < 0)
  {
    nEnd = gasEdit[nEditNum].nLineMax + nLine;
    for (nCount = nLineStart + 1; nCount <= nEnd; nCount++)
    {
      gasEdit[nEditNum].anLine[nCount]
        = gasEdit[nEditNum].anLine[nCount - nLine] + nChar;
    }
    gasEdit[nEditNum].nLineMax += nLine;
  }
  else
  {
    nEnd = gasEdit[nEditNum].nLineMax;
    for (nCount = nEnd; nCount > nLineStart; nCount--)
    {
      gasEdit[nEditNum].anLine[nCount + nLine]
        = gasEdit[nEditNum].anLine[nCount] + nChar;
    }
    for (nCount = nLineStart + 1; nCount <= nLineStart + nLine; nCount++)
    {
      gasEdit[nEditNum].anLine[nCount]
        = gasEdit[nEditNum].anLine[nLineStart];
    }
    gasEdit[nEditNum].nLineMax += nLine;
    if (nLine > 0)
    {
      gasEdit[nEditNum].nFormattedTo = nLineStart;
    }
  }
}

//////////////////////////////////////////////////////////////////
// Caret has moved out of a window
void LoseCaret (wimp_block *pcBlock)
{
  pcBlock = pcBlock;
  gboCaretLost = TRUE;
}

//////////////////////////////////////////////////////////////////
// Clear the caret structure
void ClearCaret (void)
{
  gsCaret.whWindow = wimp_BACKGROUND;
  gsCaret.nEditNum = EDIT_INVALID;
  gsCaret.nLine = -1;
  gsCaret.nChar = -1;
  gsCaret.nPos = -1;
  gsCaret.nXPos = -1;
  gsCaret.nYPos = -1;
}

//////////////////////////////////////////////////////////////////
// Create a copy space from an edit window selection
void CreateEditSelect (int nEditNum, char ** ppcMem, int * pnSize)
{
  int                         nStart;
  int                         nEnd;
  int                         nAllocSuccess;
  int                         nSize;

  if ((gsSelect.nLineStart != -1) && (gsSelect.nEmailNum != EDIT_INVALID)
    && (gsSelect.whWindow != wimp_BACKGROUND))
  {
    // Check that the selection is in the current window
    if (nEditNum == gsSelect.nEmailNum)
    {
      // Calculate the size
      nStart = gasEdit[nEditNum].anLine[gsSelect.nLineStart]
        + gsSelect.nCharStart;
      nEnd = gasEdit[nEditNum].anLine[gsSelect.nLineEnd]
        + gsSelect.nCharEnd;

      nSize = nEnd - nStart;
      if (nSize < SELECT_MEM_MIN)
      {
        nSize = SELECT_MEM_MIN;
      }

      if (*ppcMem != NULL)
      {
        nAllocSuccess = flex_extend ((flex_ptr)(ppcMem), nSize);
      }
      else
      {
        nAllocSuccess = flex_alloc ((flex_ptr)(ppcMem), nSize);
      }

      if ((nAllocSuccess) && (*ppcMem))
      {
        // Copy the text
        *pnSize = nEnd - nStart;
        memcpy (*ppcMem, gasEdit[nEditNum].szRawEmail
          + nStart, (nEnd - nStart));
      }
      else
      {
        ShowWarningTag ("Er2");
      }
    }
  }
}

//////////////////////////////////////////////////////////////////
// Count the number of non control characters in a string
int CountNonControl (char * szString, int nLength)
{
  int                         nCount;
  int                         nReturn;

  nReturn = 0;
  for (nCount = 0; nCount < nLength; nCount++)
  {
    switch (szString[nCount])
    {
      case 19:
        nCount += 7;
        break;
      case 21:
        nCount += 1;
        break;
      case 25:
        nCount += 2;
        break;
      case 26:
        nCount += 1;
        break;
      default:
        nReturn++;
        break;
    }
  }
  return nReturn;
}

//////////////////////////////////////////////////////////////////
// Clear the current selection
void ClearSelection (void)
{
  Selection                   sSelect;

  if (gsSelect.nLineStart != -1)
  {
    sSelect.nLineStart = -1;
    sSelect.whWindow = wimp_BACKGROUND;
    sSelect.nEmailNum = EDIT_INVALID;
    ChangeSelection (& sSelect);
  }
}

//////////////////////////////////////////////////////////////////
// Resize the edit window to fit the given number of lines
void SetEditHeight (int nEditNum, int nLine)
{
  os_box                sExtent;
  wimp_window_state     sWindow;

  if (gasEdit[nEditNum].whWindow != wimp_BACKGROUND)
  {
    if (nLine < EDIT_WIN_LINE_MIN)
    {
      nLine = EDIT_WIN_LINE_MIN;
    }

    sWindow.w = gasEdit[nEditNum].whWindow;
    xwimp_get_window_state (& sWindow);

    sExtent.x0 = 0;
    sExtent.x1 = gnEmailTextWidth + (2 * EMAIL_TEXT_X_OFFSET);

    sExtent.y0 = - ((nLine + 1)
      * gnEmailRowHeight) - gasEdit[nEditNum].nTextOffset;
    sExtent.y1 = 0;

    if (sExtent.y0 > (sWindow.visible.y0 - sWindow.visible.y1))
    {
      sExtent.y0 = (sWindow.visible.y0 - sWindow.visible.y1);
    }

    xwimp_set_extent (gasEdit[nEditNum].whWindow, & sExtent);
  }
}

//////////////////////////////////////////////////////////////////
// Copy the current selection to the clipboard
void CopySelectToClip (void)
{
  wimp_message                sMessage;

  CreateEditSelect (gsSelect.nEmailNum, & gpcClipboard,
    & gnClipboardSize);
  if (gpcClipboard)
  {
    sMessage.size = 24;
    sMessage.your_ref = 0;
    sMessage.action = message_CLAIM_ENTITY;
    sMessage.data.claim_entity.flags = wimp_CLAIM_CLIPBOARD;
    xwimp_send_message (wimp_USER_MESSAGE_RECORDED, & sMessage,
      wimp_BROADCAST);
    gboClipClaimed = TRUE;
  }
}

//////////////////////////////////////////////////////////////////
// Clears the clipboard
void ClearClipboard (void)
{
  int                         nAllocSuccess;

  if (gboClipClaimed)
  {
    gboClipClaimed = FALSE;
    if (gpcClipboard)
    {
      nAllocSuccess = flex_extend ((flex_ptr)(& gpcClipboard),
        SELECT_MEM_MIN);
    }
    else
    {
      nAllocSuccess = flex_alloc ((flex_ptr)(& gpcClipboard),
        SELECT_MEM_MIN);
    }

    gnClipboardSize = 0;
  }
}

//////////////////////////////////////////////////////////////////
// Send the clipboard to te requesting application
void DataRequest (wimp_block * pcBlock)
{
  if ((gboClipClaimed) && ((pcBlock->message.data.data_request.flags
    & wimp_DATA_REQUEST_CLIPBOARD) == wimp_DATA_REQUEST_CLIPBOARD))
  {
    strncpy (pcBlock->message.data.data_xfer.file_name,
      Tag ("SaveSel"), 211);
    pcBlock->message.data.data_xfer.file_name[211] = 0;
    pcBlock->message.data.data_xfer.est_size = gnClipboardSize;
    pcBlock->message.data.data_xfer.file_type = 0xfff;
    pcBlock->message.size = WORDALIGN((44 + 1 + 9));
    pcBlock->message.your_ref = pcBlock->message.my_ref;
    pcBlock->message.action = message_DATA_SAVE;

    xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
      pcBlock->message.sender);

    geSaveType = SAVETYPE_CLIPBOARD;
  }
}

//////////////////////////////////////////////////////////////////
// Clear the clipboard unless the message was sent by us
void ClaimEntity (wimp_block * pcBlock)
{
  if (pcBlock->message.sender != gnTaskHandle)
  {
    ClearClipboard ();
  }
}

//////////////////////////////////////////////////////////////////
// Paste the ontents of the clipboard
void PasteClipboard (void)
{
  wimp_message                sMessage;
  wimp_window_state           sWindowState;

  if (gboAllowMouseCaret)
  {
    sWindowState.w = gsCaret.whWindow;
    xwimp_get_window_state (& sWindowState);

    sMessage.size = 48;
    sMessage.your_ref = 0;
    sMessage.action = message_DATA_REQUEST;
    sMessage.data.data_request.w = gsCaret.whWindow;
    sMessage.data.data_request.i = -1;
    sMessage.data.data_request.pos.x = gsCaret.nXPos
      + sWindowState.visible.x0 - sWindowState.xscroll;
    sMessage.data.data_request.pos.y = gsCaret.nYPos
      + sWindowState.visible.y1 - sWindowState.yscroll + gnEmailRowHeight;
    sMessage.data.data_request.file_types[0] = 0xfff;
    sMessage.data.data_request.file_types[1] = -1;
    sMessage.data.data_request.flags = wimp_DATA_REQUEST_CLIPBOARD;
    xwimp_send_message (wimp_USER_MESSAGE, & sMessage,
      wimp_BROADCAST);
    geSaveType = SAVETYPE_CLIPBOARD;
  }
}

//////////////////////////////////////////////////////////////////
// Scroll the window if the caret is not visible
void ScrollWindowToCaret (void)
{
  wimp_window_state           sWindowState;
  int                         nWidth;
  int                         nHeight;

  if (gsCaret.whWindow != wimp_BACKGROUND)
  {
    sWindowState.w = gsCaret.whWindow;
    xwimp_get_window_state (& sWindowState);

    nWidth = sWindowState.visible.x1 - sWindowState.visible.x0;
    nHeight = sWindowState.visible.y1 - sWindowState.visible.y0;

    if (gsCaret.nXPos < sWindowState.xscroll)
    {
      sWindowState.xscroll = gsCaret.nXPos - EMAIL_TEXT_X_SCROLL;
      xwimp_open_window ((wimp_open*)(& sWindowState));
    }
    if (gsCaret.nXPos > (sWindowState.xscroll + nWidth))
    {
      sWindowState.xscroll = gsCaret.nXPos - nWidth + EMAIL_TEXT_X_SCROLL;
      xwimp_open_window ((wimp_open*)(& sWindowState));
    }
    if ((gsCaret.nYPos + gnEmailRowHeight)
      > (sWindowState.yscroll - gasEdit[gsCaret.nEditNum].nTextOffset))
    {
      sWindowState.yscroll = gsCaret.nYPos + (3 * gnEmailRowHeight)
        + gasEdit[gsCaret.nEditNum].nTextOffset;
      xwimp_open_window ((wimp_open*)(& sWindowState));
    }
    if (gsCaret.nYPos < (sWindowState.yscroll - nHeight))
    {
      sWindowState.yscroll = gsCaret.nYPos + nHeight
        - (2 * gnEmailRowHeight);
      xwimp_open_window ((wimp_open*)(& sWindowState));
    }
  }
}

//////////////////////////////////////////////////////////////////
// Activate or deactive the null poll
void SetNullPollActive (NULLPOLL ePollState)
{
  if (ePollState == NULLPOLL_ACTIVE_FAST)
  {
    gnFastPollAcc++;
    xos_read_monotonic_time (& gnMonotonicTime);
  }
  if (ePollState == NULLPOLL_DEACTIVE_FAST)
  {
    gnFastPollAcc--;
  }
  if (ePollState == NULLPOLL_ACTIVE_SLOW)
  {
    gnSlowPollAcc++;
    xos_read_monotonic_time (& gnMonotonicTime);
  }
  if (ePollState == NULLPOLL_DEACTIVE_SLOW)
  {
    gnSlowPollAcc--;
  }

  if (ePollState == NULLPOLL_ACTIVE_NETWORK)
  {
    gboNetworkPoll = TRUE;
  }

  if (ePollState == NULLPOLL_DEACTIVE_NETWORK)
  {
    gboNetworkPoll = FALSE;
  }

  if (gnFastPollAcc > 0)
  {
    gnTimeInc = NULL_POLL_FAST;
  }
  else
  {
    gnTimeInc = NULL_POLL_SLOW;
  }

  if ((gnFastPollAcc == 0) && (gnSlowPollAcc == 0))
  {
    gnTimeInc = NULL_POLL_SLOWEST;
  }

  if (gboNetworkPoll)
  {
    gnTimeInc = NULL_POLL_NETWORK;
  }

//  if ((gnFastPollAcc == 0) && (gnSlowPollAcc == 0))
//  {
//    guPollFlags |= wimp_MASK_NULL;
//  }
//  else
//  {
//    guPollFlags &= ~wimp_MASK_NULL;
//  }
}

//////////////////////////////////////////////////////////////////
// Pointer has entered a window
void PointerEntering (wimp_block * pcBlock)
{
  int                         nEmailSearch;
  bool                        boWinFound;
  wimp_w                      whWindow;

  boWinFound = FALSE;
  gboPointerEdit = FALSE;
  gnPointerEmail = EDIT_INVALID;
  whWindow = pcBlock->entering.w;

  // Check for all of the edit windows
  nEmailSearch = 0;
  while ((!boWinFound) && (gawhEditOpen[nEmailSearch] != wimp_BACKGROUND))
  {
    if (whWindow == gawhEditOpen[nEmailSearch])
    {
      gnPointerEmail = ganEditOpen[nEmailSearch];
      gboPointerEdit = TRUE;
      boWinFound = TRUE;
    }
    nEmailSearch++;
  }

  if (gnPointerEmail != EDIT_INVALID)
  {
    SetNullPollActive (NULLPOLL_ACTIVE_SLOW);
  }
}

//////////////////////////////////////////////////////////////////
// Pointer has left a window
void PointerLeaving (wimp_block * pcBlock)
{
  pcBlock = pcBlock;
  if (gnPointerEmail != EDIT_INVALID)
  {
    SetNullPollActive (NULLPOLL_DEACTIVE_SLOW);
  }
  gboPointerEdit = FALSE;
  gnPointerEmail = EDIT_INVALID;

  if (gboLinkPointer)
  {
    // Remove hand pointer
    gboLinkPointer = FALSE;
    xwimpspriteop_set_pointer_shape ("ptr_default", 97, 0, 0, NULL, NULL);
  }

  if (gboColumnPointer)
  {
    // Remove column pointer
    gboColumnPointer = FALSE;
    xwimpspriteop_set_pointer_shape ("ptr_default", 97, 0, 0, NULL, NULL);
  }
}

//////////////////////////////////////////////////////////////////
// Return the word at the given co-ordinates
void FindWordAtCoords (int nEmailNum, int nXPos, int nYPos, char ** pszWordStart, char ** pszWordEnd)
{
  wimp_window_state           sWindowState;
  int                         nXWin;
  int                         nYWin;
  int                         nLine;
  char                        *szChar;
  char                        *szLineStart = NULL;
  char                        *szLineEnd = NULL;
  char                        *szWordStart;
  char                        *szWordEnd;

  szWordStart = NULL;
  szWordEnd = NULL;
  szChar = NULL;

  sWindowState.w = gasEdit[nEmailNum].whWindow;
  xwimp_get_window_state (& sWindowState);

  nXWin = nXPos - sWindowState.visible.x0 + sWindowState.xscroll
    - EMAIL_TEXT_X_OFFSET;
  nYWin = nYPos - sWindowState.visible.y1 + sWindowState.yscroll;

  nLine = (nYWin + gasEdit[nEmailNum].nTextOffset + gnEmailTextYOffset)
    / (-gnEmailRowHeight);

  if ((nLine >= 0) && (nLine < gasEdit[nEmailNum].nLineMax))
  {
    szLineStart = gasEdit[nEmailNum].szRawEmail
      + gasEdit[nEmailNum].anLine[nLine];
    szLineEnd = gasEdit[nEmailNum].szRawEmail
      + gasEdit[nEmailNum].anLine[nLine + 1];
    xfont_scan_string (ghEmailFont,
      szLineStart,
      (font_GIVEN_FONT | font_RETURN_CARET_POS),
      MILLIPOINT * nXWin,
      MILLIPOINT * gnEmailRowHeight / 2, NULL, NULL, 0,
      & szChar, NULL, NULL, NULL);
  }

  if (szChar)
  {
    // Find the word around this point
    szWordStart = szChar;
    szWordEnd = szWordStart;
    while ((szWordStart[0] > 32) && (szWordStart >= szLineStart))
    {
      szWordStart--;
    }
    szWordStart++;
    while ((szWordEnd[0] > 32) && (szWordEnd <= szLineEnd))
    {
      szWordEnd++;
    }
    szWordEnd--;
    if (szWordStart >= szWordEnd)
    {
      szWordStart = NULL;
      szWordEnd = NULL;
    }
  }

  if (pszWordStart)
  {
    *pszWordStart = szWordStart;
  }
  if (pszWordEnd)
  {
    *pszWordEnd = szWordEnd;
  }
}

//////////////////////////////////////////////////////////////////
// Check if the word at the given memory position is a smiley
int WordIsSmiley (char * szWordStart, char * szWordEnd)
{
  int                         nSmiley;
  int                         nLength;
  int                         nReturn;

  nReturn = 0;

  nLength = szWordEnd - szWordStart + 1;

  for (nSmiley = 0; (nSmiley < gnSmileyNum) && (nReturn == 0); nSmiley++)
  {
    if (nLength == gasSmileys[nSmiley].nNameLength)
    {
      if (strncmp (szWordStart, gasSmileys[nSmiley].szName, nLength) == 0)
      {
        nReturn = nSmiley + 1;
      }
    }
  }

  return nReturn;
}

//////////////////////////////////////////////////////////////////
// Check if the word at the given memory position is a link
bool WordIsLink (char * szWordStart, char * szWordEnd)
{
  bool                        boLink;
  char                        *szFind;
  int                         nLength;

  boLink = FALSE;

  if ((szWordStart[0] == '<') && (szWordEnd[0] == '>'))
  {
    szWordStart++;
    szWordEnd--;
  }

  nLength = szWordEnd - szWordStart + 1;

  if ((nLength >= 7) && (strncmp (szWordStart, "http://", 7) == 0))
  {
    boLink = TRUE;
  }
  if ((nLength >= 4) && (strncmp (szWordStart, "www.", 4) == 0))
  {
    boLink = TRUE;
  }
  if ((nLength >= 7) && (strncmp (szWordStart, "mailto:", 7) == 0))
  {
    boLink = TRUE;
  }
  if ((nLength >= 8) && (strncmp (szWordStart, "https://", 8) == 0))
  {
    boLink = TRUE;
  }
  if ((nLength >= 4) && (strncmp (szWordStart, "URL:", 4) == 0))
  {
    boLink = TRUE;
  }

  szFind = (char *)memchr (szWordStart, '@',
    szWordEnd - szWordStart + 1);

  if (szFind && (memchr (szFind, '.', szWordEnd - szFind + 1)))
  {
    boLink = TRUE;
  }

  return boLink;
}

//////////////////////////////////////////////////////////////////
// A double click occured on a window to launch a URL
void ClickOnURL (int nEmailNum, int nXPos, int nYPos)
{
  char                        *szWordStart;
  char                        *szWordEnd;
  bool                        boLink;
  char                        szURL[1024];
  char                        szURLmail[1024+8];
  int                         nLength;
  bool                        boURLprefix;

  FindWordAtCoords (nEmailNum, nXPos, nYPos,
    & szWordStart, & szWordEnd);

  boLink = FALSE;
  if (szWordStart)
  {
    boLink = WordIsLink (szWordStart, szWordEnd);
  }
  if (boLink)
  {
    if ((szWordStart[0] == '<') && (szWordEnd[0] == '>'))
    {
      szWordStart++;
      szWordEnd--;
    }
    nLength = MIN(((int)sizeof(szURL) - 8), (szWordEnd - szWordStart + 1));

    if ((nLength >= 4) && (strncmp (szWordStart, "URL:", 4) == 0))
    {
      nLength -= 4;
      strncpy (szURL, szWordStart + 4, nLength);
      boURLprefix = TRUE;
    }
    else
    {
      strncpy (szURL, szWordStart, nLength);
      boURLprefix = FALSE;
    }
    szURL[nLength] = 0;
    if ((strncmp (szURL, "http://", 7) == 0)
      || (strncmp (szURL, "www.", 4) == 0)
      || (strncmp (szURL, "mailto:", 7) == 0)
      || (strncmp (szURL, "https://", 8) == 0)
      | (boURLprefix))
    {
      OpenURL (szURL);
    }
    else
    {
      sprintf (szURLmail, "mailto:%s", szURL);
      OpenURL (szURLmail);
    }
  }
}

//////////////////////////////////////////////////////////////////
// Show or hide the advanced edit options
void ShowButtons (int nEditNum, bool boShow)
{
  wimp_window_state           sWindowState;
  wimp_draw                   sRedraw;
  bool                        boMore;
  wimp_window_info            sWindowInfo;
  wimp_caret                  sCaret;

  gasEdit[nEditNum].boButtons = boShow;

  // Change the header extent
  sWindowInfo.w = gasEdit[nEditNum].whHead;
  xwimp_get_window_info_header_only (& sWindowInfo);

  if (boShow)
  {
    gasEdit[nEditNum].nTextOffset = EDIT_TEXT_Y_OFFSET_MAX;
    sWindowInfo.extent.y0 = - EDIT_HEAD_EXTENT_MAX;

    xwimp_set_extent (sWindowInfo.w, & sWindowInfo.extent);

    // Open the pane in the correct place
    sWindowState.w = gasEdit[nEditNum].whWindow;
    xwimp_get_window_state (& sWindowState);
    sWindowState.w = gasEdit[nEditNum].whHead;
    xwimp_open_window ((wimp_open *)& sWindowState);
  }
  else
  {
    gasEdit[nEditNum].nTextOffset = EDIT_TEXT_Y_OFFSET_MIN;
    sWindowInfo.extent.y0 = - EDIT_HEAD_EXTENT_MIN;

    xwimp_set_extent (sWindowInfo.w, & sWindowInfo.extent);

    // Close the pane
    CloseWindowHandle (gasEdit[nEditNum].whHead);
  }
  xwimp_get_caret_position (& sCaret);

  // Move the caret if necessary
  if (gsCaret.nEditNum == nEditNum)
  {
    SetCaretPos (nEditNum, gsCaret.nLine, gsCaret.nChar);
  }

  // Redraw the entire window
  sWindowState.w = gasEdit[nEditNum].whWindow;
  xwimp_get_window_state (& sWindowState);

  sRedraw.w = gasEdit[nEditNum].whWindow;
  sRedraw.box.x0 = sWindowState.xscroll;
  sRedraw.box.x1 = sWindowState.visible.x1 - sWindowState.visible.x0
    + sWindowState.xscroll;
  sRedraw.box.y0 = sWindowState.visible.y0 - sWindowState.visible.y1
    + sWindowState.yscroll;
  sRedraw.box.y1 = sWindowState.yscroll;
  xwimp_update_window (& sRedraw, & boMore);

  while (boMore)
  {
    RedrawEdit (& sRedraw, nEditNum);
    xwimp_get_rectangle (& sRedraw, & boMore);
  }
}

//////////////////////////////////////////////////////////////////
// Open the iconbar menu
void SetupIconbarMenu (void)
{
  SetMenuItemGreyness (!gboAccountsLoaded, gpcIconBarMenu, 3);
  SetMenuItemGreyness (!gboAccountsLoaded, gpcIconBarMenu, 4);

  SetMenuItemGreyness (((!gboAccountsLoaded)
    || (strcmp (gszUnlockPassword, "") == 0)), gpcIconBarMenu, 5);
}



//////////////////////////////////////////////////////////////////
// Compare two strings
int cmpstr (char * szString1, char * szString2)
{
  int                   nPos;
  int                   nReturn = 0;
  char                  cChar1;
  char                  cChar2;

  nPos = -1;
  do
  {
    nPos++;

    cChar1 = szString1[nPos];
    cChar2 = szString2[nPos];

    if ((cChar1 >= 'A') && (cChar1 <= 'Z'))
    {
      cChar1 += ('a' - 'A');
    }
    if ((cChar2 >= 'A') && (cChar2 <= 'Z'))
    {
      cChar2 += ('a' - 'A');
    }

    if (cChar1 < cChar2)
    {
      nReturn = +1;
    }
    if (cChar1 > cChar2)
    {
      nReturn = -1;
    }
  } while ((nReturn == 0) && (cChar1 != 0)  && (cChar2 != 0));

  return nReturn;
}


//////////////////////////////////////////////////////////////////
// Create an email to a particular address
int CreateEditWindowAddress (char * szAddress)
{
  int                         nEditNum;

  nEditNum = CreateEditWindow ();
  if (nEditNum >= 0)
  {
    SetIconText (szAddress, gasEdit[nEditNum].whHead, (wimp_i)0);
  }

  return nEditNum;
}



//////////////////////////////////////////////////////////////////
// Attach a submenu to a menu
void SetSubMenu (wimp_menu * pcMain, int nItem, wimp_menu * pcSub)
{
  pcMain->entries[nItem].sub_menu = pcSub;
}

//////////////////////////////////////////////////////////////////
// Set the greyed out status of a menu item
void SetMenuItemGreyness (bool boState, wimp_menu * pcMenu, int nItem)
{
  if (boState)
  {
    pcMenu->entries[nItem].icon_flags |= wimp_ICON_SHADED;
  }
  else
  {
    pcMenu->entries[nItem].icon_flags &= ~wimp_ICON_SHADED;
  }
}

//////////////////////////////////////////////////////////////////
// Set the ticked status of a menu item
void SetMenuItemTicked (bool boTicked, wimp_menu * pcMenu, int nItem)
{
  if (boTicked)
  {
    pcMenu->entries[nItem].menu_flags |= wimp_MENU_TICKED;
  }
  else
  {
    pcMenu->entries[nItem].menu_flags &= ~wimp_MENU_TICKED;
  }
}

//////////////////////////////////////////////////////////////////
// Set up the new email menu
void SetUpNewMenu (void)
{
  bool                        boGreyEdit = TRUE;

  if (gsCaret.nEditNum == gnEmailSelected)
  {
    boGreyEdit = FALSE;
    SetMenuItemGreyness (FALSE, gpcNewEditMenu, 2);
  }
  else
  {
    SetMenuItemGreyness (TRUE, gpcNewEditMenu, 2);
  }

  if (gsSelect.nEmailNum == gnEmailSelected)
  {
    boGreyEdit = FALSE;
    SetMenuItemGreyness (FALSE, gpcNewEditMenu, 0);
    SetMenuItemGreyness (FALSE, gpcNewEditMenu, 1);
    SetMenuItemGreyness (FALSE, gpcNewEditMenu, 3);
  }
  else
  {
    SetMenuItemGreyness (TRUE, gpcNewEditMenu, 0);
    SetMenuItemGreyness (TRUE, gpcNewEditMenu, 1);
    SetMenuItemGreyness (TRUE, gpcNewEditMenu, 3);
  }

  SetMenuItemGreyness (boGreyEdit, gpcNewMenu, 1);

  SetMenuItemTicked (gasEdit[gnEmailSelected].boButtons,
    gpcNewMenu, 3);
}

//////////////////////////////////////////////////////////////////
// Redraw all of the email and edit windows
void RedrawAllEmailEditWindows (void)
{
  int                         nEmailSearch;
  wimp_window_state           sWindowState;

  // Redraw all of the Email and Edit windows
  nEmailSearch = 0;
  while (gawhEditOpen[nEmailSearch] != wimp_BACKGROUND)
  {
    sWindowState.w = gawhEditOpen[nEmailSearch];
    xwimp_get_window_state (& sWindowState);
    err (xwimp_force_redraw (gawhEditOpen[nEmailSearch],
      0, sWindowState.visible.y0 - sWindowState.visible.y1,
      sWindowState.visible.x1 - sWindowState.visible.x0, 0));
    nEmailSearch++;
  }
}


//////////////////////////////////////////////////////////////////
// The effect of clicking on the Continue button of a query box
void QueryContinue (void)
{
  bool                        boWinFound;
  int                         nEditSearch;

  CloseQuery ();

  switch (geQueryAction)
  {
    case QUERYACTION_QUIT:
      Quit ();
      break;
    case QUERYACTION_CLOSEEDIT:
      boWinFound = FALSE;
      nEditSearch = 0;
      while ((!boWinFound) && (gawhEditOpen[nEditSearch] != wimp_BACKGROUND))
      {
        if (gwhWindowClose == gawhEditOpen[nEditSearch])
        {
          err (xwimp_close_window (gwhWindowClose));
          err (xwimp_close_window
            (gasEdit[ganEditOpen[nEditSearch]].whHead));
          DestroyEditWindow (ganEditOpen[nEditSearch]);
          boWinFound = TRUE;
        }
        nEditSearch++;
      }
      gwhWindowClose = wimp_BACKGROUND;
      break;
    case QUERYACTION_NONE:
    default:
      break;
  }
}

//////////////////////////////////////////////////////////////////
// The effect of clicking on the Cancel button of a query box
void QueryCancel (void)
{
  CloseQuery ();

  switch (geQueryAction)
  {
    case QUERYACTION_QUIT:
    case QUERYACTION_NONE:
    case QUERYACTION_CLOSEEDIT:
    default:
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Extend the memory allocated for formatting information
int ExtendLineMem (int nEditNum)
{
  int                   nAllocSuccess;
  int                   nNewSize;

  nNewSize = gasEdit[nEditNum].nLineMem + (EDIT_LINE_BLOCK * sizeof(int));
  nAllocSuccess = flex_extend ((flex_ptr)& gasEdit[nEditNum].anLine,
    nNewSize);
  if (nAllocSuccess == 1)
  {
    gasEdit[nEditNum].nLineMem = nNewSize;
  }
  else
  {
    ShowWarningTag ("Er25");
  }

  return nAllocSuccess;
}

//////////////////////////////////////////////////////////////////
// Extend the memory allocated for the email text
int ExtendEmailMem (int nEditNum)
{
  int                   nAllocSuccess;
  int                   nNewSize;

  nNewSize = gasEdit[nEditNum].nEmailMem + (EDIT_CHARS_BLOCK * sizeof(char));
  nAllocSuccess = flex_extend ((flex_ptr)& gasEdit[nEditNum].szRawEmail,
    nNewSize);
  if (nAllocSuccess == 1)
  {
    gasEdit[nEditNum].nEmailMem = nNewSize;
  }
  else
  {
    ShowWarningTag ("Er25");
  }

  return nAllocSuccess;
}

//////////////////////////////////////////////////////////////////
// Extend the memory allocated for formatting information
int ExtendLineMemTo (int nEditNum, int nSize)
{
  int                   nAllocSuccess = 1;
  int                   nNewSize;

  if (nSize > gasEdit[nEditNum].nLineMem)
  {
    nNewSize = ((EDIT_LINE_BLOCK * sizeof(int))
      * (nSize / (EDIT_LINE_BLOCK * sizeof(int))))
      + (EDIT_LINE_BLOCK * sizeof(int));
    nAllocSuccess = flex_extend ((flex_ptr)& gasEdit[nEditNum].anLine,
      nNewSize);
    if (nAllocSuccess == 1)
    {
      gasEdit[nEditNum].nLineMem = nNewSize;
    }
    else
    {
      ShowWarningTag ("Er25");
    }
  }

  return nAllocSuccess;
}

//////////////////////////////////////////////////////////////////
// Extend the memory allocated for the email text
int ExtendEmailMemTo (int nEditNum, int nSize)
{
  int                   nAllocSuccess = 1;
  int                   nNewSize;

  if (nSize >= gasEdit[nEditNum].nEmailMem)
  {
    nNewSize = ((EDIT_CHARS_BLOCK * sizeof(char))
      * (nSize / (EDIT_CHARS_BLOCK * sizeof(char))))
      + (EDIT_CHARS_BLOCK * sizeof(char));
    nAllocSuccess = flex_extend ((flex_ptr)& gasEdit[nEditNum].szRawEmail,
      nNewSize);
    if (nAllocSuccess == 1)
    {
      gasEdit[nEditNum].nEmailMem = nNewSize;
    }
    else
    {
      ShowWarningTag ("Er25");
    }
  }

  return nAllocSuccess;
}

//////////////////////////////////////////////////////////////////
// Change the local and remote font
bool ChangeListFonts (char * szFont, char * szFontB)
{
  int                         nFontMinX;
  int                         nFontMinY;
  int                         nFontMaxX;
  int                         nFontMaxY;
  int                         nXRes;
  int                         nYRes;
  bool                        boReturn = FALSE;

  if ((strncmp (gszFont, szFont, sizeof (gszFont)) != 0)
    || (strncmp (gszFontB, szFontB, sizeof (gszFontB)) != 0))
  {
    xfont_lose_font (ghFont);
    xfont_lose_font (ghFontB);
    if (strncmp (szFont, "", sizeof (gszFont)) != 0)
    {
      strncpy (gszFont, szFont, sizeof (gszFont));
    }
    else
    {
      strncpy (gszFont, "Trinity.Medium", sizeof (gszFont));
    }

    if (strncmp (szFontB, "", sizeof (gszFontB)) != 0)
    {
      strncpy (gszFontB, szFontB, sizeof (gszFontB));
    }
    else
    {
      strncpy (gszFontB, "Trinity.Bold", sizeof (gszFontB));
    }

    // Find Font
    xfont_find_font (gszFont, 12 * 16, 12 * 16, 0, 0, & ghFont, & nXRes,
      & nYRes);
    xfont_find_font (gszFontB, 12 * 16, 12 * 16, 0, 0, & ghFontB, & nXRes,
      & nYRes);

    xfont_read_info (ghFont, & nFontMinX, & nFontMinY, & nFontMaxX,
      & nFontMaxY);
    gnRowHeight = nFontMaxY - nFontMinY;
    gnTextYOffset = - nFontMinY;

    xfont_read_info (ghFontB, & nFontMinX, & nFontMinY, & nFontMaxX,
      & nFontMaxY);
    if ((nFontMaxY - nFontMinY) > gnRowHeight)
    {
      gnRowHeight = nFontMaxY - nFontMinY;
      gnTextYOffset = - nFontMinY;
    }

    xfont_scan_string (ghFont, "", (font_GIVEN_FONT
      | font_GIVEN_LENGTH), MILLIPOINT * 64, MILLIPOINT * gnRowHeight,
      NULL, NULL, 1, NULL, & gnEllipsisWidth, NULL, NULL);
    xfont_scan_string (ghFontB, "", (font_GIVEN_FONT
      | font_GIVEN_LENGTH), MILLIPOINT * 64, MILLIPOINT * gnRowHeight,
      NULL, NULL, 1, NULL, & gnEllipsisWidthB, NULL, NULL);

    boReturn = TRUE;
  }

  return boReturn;
}

//////////////////////////////////////////////////////////////////
// Find all of the additional fonts
void FindAdditionalFonts (void)
{
  int                         nFontMinX;
  int                         nFontMinY;
  int                         nFontMaxX;
  int                         nFontMaxY;
//  char                        szFont[] = "Trinity.Medium";
//  char                        szFontB[] = "Trinity.Bold";
  char                        szEmailFont[] = "Corpus.Medium";
  char                        szEmailBFont[] = "Corpus.Bold";
  char                        szEmailIFont[] = "Corpus.Medium.Oblique";
  char                        szEmailBIFont[] = "Corpus.Bold.Oblique";
  int                         nXRes;
  int                         nYRes;

  // Find Font
  xfont_find_font (gszFont, 12 * 16, 12 * 16, 0, 0, & ghFont, & nXRes,
    & nYRes);
  xfont_find_font (gszFontB, 12 * 16, 12 * 16, 0, 0, & ghFontB, & nXRes,
    & nYRes);

  xfont_read_info (ghFont, & nFontMinX, & nFontMinY, & nFontMaxX,
    & nFontMaxY);
  gnRowHeight = nFontMaxY - nFontMinY;
  gnTextYOffset = - nFontMinY;

  xfont_read_info (ghFontB, & nFontMinX, & nFontMinY, & nFontMaxX,
    & nFontMaxY);
  if ((nFontMaxY - nFontMinY) > gnRowHeight)
  {
    gnRowHeight = nFontMaxY - nFontMinY;
    gnTextYOffset = - nFontMinY;
  }

  xfont_scan_string (ghFont, "", (font_GIVEN_FONT
    | font_GIVEN_LENGTH), MILLIPOINT * 64, MILLIPOINT * gnRowHeight,
    NULL, NULL, 1, NULL, & gnEllipsisWidth, NULL, NULL);
  xfont_scan_string (ghFontB, "", (font_GIVEN_FONT
    | font_GIVEN_LENGTH), MILLIPOINT * 64, MILLIPOINT * gnRowHeight,
    NULL, NULL, 1, NULL, & gnEllipsisWidthB, NULL, NULL);

  // Find email font
  xfont_find_font (szEmailFont, 12 * 16, 12 * 16, 0, 0, & ghEmailFont,
    & nXRes, & nYRes);
  xfont_find_font (szEmailBFont, 12 * 16, 12 * 16, 0, 0,
    & ghEmailBFont, & nXRes, & nYRes);
  xfont_find_font (szEmailIFont, 12 * 16, 12 * 16, 0, 0,
    & ghEmailIFont, & nXRes, & nYRes);
  xfont_find_font (szEmailBIFont, 12 * 16, 12 * 16, 0, 0,
    & ghEmailBIFont, & nXRes, & nYRes);
  xfont_read_info (ghEmailFont, & nFontMinX, & nFontMinY, & nFontMaxX,
    & nFontMaxY);
  gnEmailRowHeight = nFontMaxY - nFontMinY;
  gnEmailTextYOffset = - nFontMinY;

  xfont_scan_string (ghEmailFont, "A", (font_GIVEN_FONT
    | font_GIVEN_LENGTH), MILLIPOINT * 64, MILLIPOINT * gnEmailRowHeight,
    NULL, NULL, 1, NULL, & gnEmailTextWidth, NULL, NULL);
  gnEmailCharWidth = gnEmailTextWidth / MILLIPOINT;
  gnEmailTextWidth = (EMAIL_CHAR_WIDTH * gnEmailTextWidth) / MILLIPOINT;
}

//////////////////////////////////////////////////////////////////
// Release all of the additional fonts
void ReleaseAdditionalFonts (void)
{
  xfont_lose_font (ghFont);
  xfont_lose_font (ghFontB);
  xfont_lose_font (ghEmailFont);
  xfont_lose_font (ghEmailBFont);
  xfont_lose_font (ghEmailIFont);
  xfont_lose_font (ghEmailBIFont);
}

//////////////////////////////////////////////////////////////////
// Response to a Mode Change
void ModeChange (void)
{
  ReleaseAdditionalFonts ();
  FindAdditionalFonts ();

  GenerateTransTable ();
}

//////////////////////////////////////////////////////////////////
// Generate sprite palette translation table
void GenerateTransTable (void)
{
  int                         nSize;

  if (gpsTransTable)
  {
    free (gpsTransTable);
    gpsTransTable = NULL;
  }

  if (gpcEmoticons->sprite_count > 0)
  {
    err (xcolourtrans_generate_table_for_sprite (gpcEmoticons,
      (osspriteop_id)((char*)gpcEmoticons + gpcEmoticons->first),
      colourtrans_CURRENT_MODE, colourtrans_CURRENT_PALETTE,
      NULL, colourtrans_GIVEN_SPRITE,
      NULL, NULL, & nSize));

    gpsTransTable = (osspriteop_trans_tab*)malloc (nSize);

    err (xcolourtrans_generate_table_for_sprite (gpcEmoticons,
      (osspriteop_id)((char*)gpcEmoticons + gpcEmoticons->first),
      os_CURRENT_MODE, colourtrans_CURRENT_PALETTE,
      gpsTransTable, colourtrans_GIVEN_SPRITE,
      NULL, NULL, NULL));
  }
}


//////////////////////////////////////////////////////////////////
// Set up the font list
void SetUpFontMenu (wimp_menu * pcMenu, char * szFont)
{
  xfont_list_fonts ((byte *)(pcMenu), font_RETURN_FONT_MENU
    | font_GIVEN_TICK, FONT_LIST_MAX, gpcFontMenuBuffer, FONT_LIST_IND_MAX,
    szFont, NULL, NULL, NULL);
}

////////////////////////////////////////////////////////////////////
//// Open the local email font list
//void OpenChoicesLocalFontMenu (void)
//{
//  wimp_window_state           sWindowState;
//  wimp_icon_state             sIconState;
//  int                         nXPos;
//  int                         nYPos;
//
//  sWindowState.w = gwhChce;
//  xwimp_get_window_state (& sWindowState);
//
//  sIconState.w = gwhChce;
//  sIconState.i = 20;
//  xwimp_get_icon_state (& sIconState);
//
//  nXPos = sWindowState.visible.x0 - sWindowState.xscroll
//    + sIconState.icon.extent.x1;
//  nYPos = sWindowState.visible.y1 - sWindowState.yscroll
//    + sIconState.icon.extent.y1;
//
//  SetUpFontMenu (gpcLocalFontMenu, gszLocalChoiceFont);
//
//  OpenMenu (gpcLocalFontMenu, nXPos, nYPos);
//}
//
////////////////////////////////////////////////////////////////////
//// Select the local email font
//void ChoicesLocalFontSelect (wimp_selection * psSelection)
//{
//  char                        szFontBuffer[FONT_NAME_LENGTH] = "";
//  int                         nEnd;
//
//  xfont_decode_menu (0, (byte *)(gpcLocalFontMenu), (byte *)(psSelection),
//    szFontBuffer, sizeof (szFontBuffer), NULL, NULL);
//
//  if (strncmp (szFontBuffer, "\\F", 2) == 0)
//  {
//    nEnd = 2;
//    while ((nEnd < (int)sizeof (szFontBuffer)) && (szFontBuffer[nEnd] > 0x20)
//      && (szFontBuffer[nEnd] != '\\'))
//    {
//      nEnd++;
//    }
//    strncpy (gszLocalChoiceFont, szFontBuffer + 2, nEnd - 2);
//    gszLocalChoiceFont[nEnd - 2] = 0;
//  }
//
//  SetIconText (gszLocalChoiceFont, gwhChce, 19);
//}
//
////////////////////////////////////////////////////////////////////
//// Open the remote email font list
//void OpenChoicesRemoteFontMenu (void)
//{
//  wimp_window_state           sWindowState;
//  wimp_icon_state             sIconState;
//  int                         nXPos;
//  int                         nYPos;
//
//  sWindowState.w = gwhChce;
//  xwimp_get_window_state (& sWindowState);
//
//  sIconState.w = gwhChce;
//  sIconState.i = 23;
//  xwimp_get_icon_state (& sIconState);
//
//  nXPos = sWindowState.visible.x0 - sWindowState.xscroll
//    + sIconState.icon.extent.x1;
//  nYPos = sWindowState.visible.y1 - sWindowState.yscroll
//    + sIconState.icon.extent.y1;
//
//  SetUpFontMenu (gpcRemoteFontMenu, gszRemoteChoiceFont);
//
//  OpenMenu (gpcRemoteFontMenu, nXPos, nYPos);
//}
//
////////////////////////////////////////////////////////////////////
//// Select the remote email font
//void ChoicesRemoteFontSelect (wimp_selection * psSelection)
//{
//  char                        szFontBuffer[FONT_NAME_LENGTH] = "";
//  int                         nEnd;
//
//  xfont_decode_menu (0, (byte *)(gpcRemoteFontMenu), (byte *)(psSelection),
//    szFontBuffer, sizeof (szFontBuffer), NULL, NULL);
//
//  if (strncmp (szFontBuffer, "\\F", 2) == 0)
//  {
//    nEnd = 2;
//    while ((nEnd < (int)sizeof (szFontBuffer)) && (szFontBuffer[nEnd] > 0x20)
//      && (szFontBuffer[nEnd] != '\\'))
//    {
//      nEnd++;
//    }
//    strncpy (gszRemoteChoiceFont, szFontBuffer + 2, nEnd - 2);
//    gszRemoteChoiceFont[nEnd - 2] = 0;
//  }
//
//  SetIconText (gszRemoteChoiceFont, gwhChce, 22);
//}
//
//////////////////////////////////////////////////////////////////
// A URL Launch message has been received; see if we should respond
void LaunchURL (wimp_block * pcBlock)
{
  int                         nLength;

  nLength = pcBlock->message.size - 20;

  if (nLength > 7)
  {
    if (strncmp ((char *)pcBlock->message.data.reserved, "mailto:", 7)
      == 0)
    {
      // Open an email window with the correct address
      CreateEditWindowAddress (
        (char *)(pcBlock->message.data.reserved + 7));
      // Acknowledge the message
      pcBlock->message.your_ref = pcBlock->message.my_ref;
      xwimp_send_message (wimp_USER_MESSAGE_ACKNOWLEDGE,
        & pcBlock->message, pcBlock->message.sender);
    }
  }
}


//////////////////////////////////////////////////////////////////
// Warn that a submenu is about to be opened
void MenuWarning (wimp_block *pcBlock)
{
  int                         nDepth;

  if (gpcMenuCurrent == gpcNewMenu)
  {
    nDepth = 0;
    while (((int*)
      ((char*)(& pcBlock->message.data.reserved) + 12))[nDepth] != -1)
    {
      nDepth++;
    }
    nDepth--;
  }
}


//////////////////////////////////////////////////////////////////
// Respond to a wimp scroll request
void ScrollRequest (wimp_block *pcBlock)
{
  int                         nXScroll;
  int                         nYScroll;
  int                         nPaneHeight = 0;
  int                         nColumnWidth = DEFAULT_X_SCROLL;
  int                         nRowHeight = DEFAULT_Y_SCROLL;
  bool                        boYSnap = FALSE;
  int                         nEditSearch;
  bool                        boWinFound = FALSE;
  wimp_w                      whWindow;

  whWindow = pcBlock->scroll.w;
  nXScroll = pcBlock->scroll.xscroll;
  nYScroll = pcBlock->scroll.yscroll;

  if (!boWinFound)
  {
    nPaneHeight = EMAIL_TEXT_Y_OFFSET;

    nEditSearch = 0;
    while ((!boWinFound) && (gawhEditOpen[nEditSearch] != wimp_BACKGROUND))
    {
      if (whWindow == gawhEditOpen[nEditSearch])
      {
        nPaneHeight = gasEdit[ganEditOpen[nEditSearch]].nTextOffset;
        boWinFound = TRUE;
      }
      nEditSearch++;
    }

    nColumnWidth = gnEmailCharWidth;
    nRowHeight = gnEmailRowHeight;
    boWinFound = TRUE;
    boYSnap = TRUE;
  }

  switch (pcBlock->scroll.xmin)
  {
    default:
    case wimp_SCROLL_NONE:
      // Do nothing
      break;
    case wimp_SCROLL_COLUMN_RIGHT:
      nXScroll += nColumnWidth;
      break;
    case wimp_SCROLL_COLUMN_LEFT:
      nXScroll -= nColumnWidth;
      break;
    case wimp_SCROLL_PAGE_RIGHT:
      nXScroll += pcBlock->scroll.visible.x1 - pcBlock->scroll.visible.x0;
      break;
    case wimp_SCROLL_PAGE_LEFT:
      nXScroll -= pcBlock->scroll.visible.x1 - pcBlock->scroll.visible.x0;
      break;
  }
  switch (pcBlock->scroll.ymin)
  {
    default:
    case wimp_SCROLL_NONE:
      // Do nothing
      break;
    case wimp_SCROLL_LINE_UP:
      nYScroll += nRowHeight;
      if (boYSnap)
      {
        nYScroll = ((nYScroll - nRowHeight + 1) / nRowHeight) * nRowHeight;
      }
      break;
    case wimp_SCROLL_LINE_DOWN:
      nYScroll -= nRowHeight;
      if (boYSnap)
      {
        nYScroll = (nYScroll / nRowHeight) * nRowHeight;
      }
      break;
    case wimp_SCROLL_PAGE_UP:
      nYScroll += pcBlock->scroll.visible.y1 - pcBlock->scroll.visible.y0
        - nPaneHeight + PAGE_SCROLL_Y_ADD;
      break;
    case wimp_SCROLL_PAGE_DOWN:
      nYScroll -= pcBlock->scroll.visible.y1 - pcBlock->scroll.visible.y0
        - nPaneHeight + PAGE_SCROLL_Y_ADD;
      break;
  }

  pcBlock->scroll.xscroll = nXScroll;
  pcBlock->scroll.yscroll = nYScroll;

  OpenWindow (pcBlock);
}

//////////////////////////////////////////////////////////////////
// Sets the delete/backspace key type: either OS4, OS5 or guess
void SetDeleteType (DELETETYPE eDeleteType)
{
  int                         nOSVersion;

  geDeleteTypeConfig = eDeleteType;
  switch (eDeleteType)
  {
    case DELETETYPE_OS4:
      gboIyonixDelete = FALSE;
      break;
    case DELETETYPE_OS5:
      gboIyonixDelete = TRUE;
      break;
    default:
    case DELETETYPE_GUESS:
      xosbyte1 (osbyte_IN_KEY, 0, 0xff, & nOSVersion);
      if (nOSVersion >= 0xaa)
      {
        gboIyonixDelete = TRUE;
      }
      else
      {
        gboIyonixDelete = FALSE;
      }
      break;
  }
}

//////////////////////////////////////////////////////////////////
// Find a character in a string of given length
char * strnchr (char * szString, char cChar, int nLength)
{
  char                        *szReturn;

  szReturn = NULL;
  while ((szString[0] != 0) && (nLength > 0))
  {
    if (szString[0] == cChar)
    {
      nLength = 0;
      szReturn = szString;
    }
    szString++;
    nLength--;
  }

  return szReturn;
}

//////////////////////////////////////////////////////////////////
// Find a character in a string of given length ignoring zero terminators
char * strnzchr (char * szString, char cChar, int nLength)
{
  char                        *szReturn;

  szReturn = NULL;
  while (nLength > 0)
  {
    if (szString[0] == cChar)
    {
      nLength = 0;
      szReturn = szString;
    }
    szString++;
    nLength--;
  }

  return szReturn;
}

//////////////////////////////////////////////////////////////////
// Copy a string adding linefeeds
int ConvertString (char * szOutput, char * szInput, int nBufferLen)
{
  int                         nPosTo;
  int                         nPosFrom;

  nPosTo = 0;
  nPosFrom = 0;
  while ((szInput[nPosFrom] >= 32) && (nPosTo < (nBufferLen - 1)))
  {
    if (szInput[nPosFrom] == '\\')
    {
      nPosFrom++;
      if (szInput[nPosFrom] == 'n')
      {
        szOutput[nPosTo] = '\n';
        nPosFrom++;
      }
      else
      {
        if (szInput[nPosFrom] != 0)
        {
          szOutput[nPosTo] = '\\';
          nPosFrom++;
        }
      }
    }
    else
    {
      szOutput[nPosTo] = szInput[nPosFrom];
      nPosFrom++;
    }
    nPosTo++;
  }
  szOutput[nPosTo] = 0;

  return nPosTo;
}

//////////////////////////////////////////////////////////////////
// Sets up the email list for the emails to be saved
void SetEmailSave (int nEmailNum)
{
  ganEmailSave[0] = nEmailNum;
  gnEmailSaveNum = 1;
  gnEmailSavePos = 0;
}

//////////////////////////////////////////////////////////////////
// Calculates the size of the emails to be save
int EmailSaveSize (void)
{
  int                         nCount;
  int                         nSize = 0;

  for (nCount = 0; nCount < gnEmailSaveNum; nCount++)
  {
    nSize += gasEdit[ganEmailSave[nCount]].nSize;
  }
  return nSize;
}

//////////////////////////////////////////////////////////////////
// Receive message LINK_CONTROL
void ReceiveLinkControl (wimp_block *pcBlock)
{
  int                         nAction;
  int                         nIdentifier;
  MemFile                     *psConfigFile;
  char                        szSection[256];
  char                        *szFilename;
  int                         nSection;

  nAction = ((int *)(pcBlock->message.data.reserved))[1];

  switch (nAction)
  {
    case 0: // Init
      gnLinkHandle = ((int *)(pcBlock->message.data.reserved))[0];
      ghLinkTask = pcBlock->message.sender;
      ActOnInit ();
      break;
    case 1: // Config
      ActOnConfig ();
      break;
    case 2: // Save config
      nIdentifier = ((int *)(pcBlock->message.data.reserved))[0];
      szFilename = pcBlock->message.data.reserved + 8;
      psConfigFile = memopen (szFilename, "a");
      sprintf (szSection, "Component %d", nIdentifier);
      SaveSectionMem (psConfigFile, szSection);
      ActOnConfigSave (psConfigFile);
      memclose (psConfigFile);
      err (xosfile_set_type (szFilename, COMPOSE_FILETYPE));
      break;
    case 3: // Load config
      nIdentifier = ((int *)(pcBlock->message.data.reserved))[0];
      szFilename = pcBlock->message.data.reserved + 8;
      psConfigFile = memopen (szFilename, "r");
      sprintf (szSection, "Component %d", nIdentifier);
      nSection = FindSectionMem (psConfigFile, szSection);
      ActOnConfigLoad (psConfigFile, nSection);
      memclose (psConfigFile);
      break;
  }

  REPORTVAR ("RECV- LinkControl handle = %d", gnLinkHandle);
  REPORTVAR ("      LinkControl action = %d", nAction);
}

//////////////////////////////////////////////////////////////////
// Receive message LINK_OPEN
void ReceiveLinkOpen (wimp_block *pcBlock)
{
  int                         nLinkHandle;
  int                         nLink;

  nLinkHandle = ((int *)(pcBlock->message.data.reserved))[0];
  if (nLinkHandle == gnLinkHandle)
  {
    nLink = ((int *)(pcBlock->message.data.reserved))[1];

    ActOnLinkOpen (nLink);

    REPORTVAR ("RECV: LinkOpen handle = %d", nLinkHandle);
    REPORTVAR ("      LinkOpen link = %d", nLink);
  }
}

//////////////////////////////////////////////////////////////////
// Receive message LINK_SEND
void ReceiveLinkSend (wimp_block *pcBlock)
{
  int                         nLinkHandle;
  int                         nLink;
  int                         nSize;
  char                        *pcData;

  nLinkHandle = ((int *)(pcBlock->message.data.reserved))[0];
  if (nLinkHandle == gnLinkHandle)
  {
    nLink = ((int *)(pcBlock->message.data.reserved))[1];
    nSize = ((int *)(pcBlock->message.data.reserved))[2];
    pcData =  pcBlock->message.data.reserved + 12;

    ActOnLinkSend (nLink, pcData, nSize);

    REPORTVAR ("RECV: LinkSend handle = %d", gnLinkHandle);
    REPORTVAR ("      LinkSend link = %d", nLink);
    REPORTVAR ("      LinkSend size = %d", nSize);
  }
}

//////////////////////////////////////////////////////////////////
// Receive message LINK_CLOSE
void ReceiveLinkClose (wimp_block *pcBlock)
{
  int                         nLinkHandle;
  int                         nLink;

  nLinkHandle = ((int *)(pcBlock->message.data.reserved))[0];
  if (nLinkHandle == gnLinkHandle)
  {
    nLink = ((int *)(pcBlock->message.data.reserved))[1];

    ActOnLinkClose (nLink);

    REPORTVAR ("RECV: LinkClose handle = %d", gnLinkHandle);
    REPORTVAR ("      LinkClose link = %d", nLink);
  }
}

//////////////////////////////////////////////////////////////////
// Receive message LINK_DATASAVE
void ReceiveLinkDataSave (wimp_block *pcBlock)
{
  int                         nLinkHandle;
  int                         nLink;

  nLinkHandle = ((int *)(pcBlock->message.data.reserved))[0];
  if (nLinkHandle == gnLinkHandle)
  {
    nLink = ((int *)(pcBlock->message.data.reserved))[1];

    REPORTVAR ("RECV: LinkDataSave handle = %d", gnLinkHandle);
    REPORTVAR ("      LinkDataSave link = %d", nLink);

    // Reply with a RamFetch message
    ((int *)(pcBlock->message.data.reserved))[2] = (int)gpcLinkBuffer;
    ((int *)(pcBlock->message.data.reserved))[3] = LINK_RAM_BUFFER;

    pcBlock->message.size = 36;
    pcBlock->message.your_ref = pcBlock->message.my_ref;
    pcBlock->message.action = message_LINK_RAMFETCH;

    xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
      pcBlock->message.sender);

    REPORTVAR ("SEND: LinkRamFetch handle = %d", gnLinkHandle);
    REPORTVAR ("      LinkRamFetch link = %d", nLink);
    REPORTVAR ("      LinkRamFetch size = %d", LINK_RAM_BUFFER);
  }
}

//////////////////////////////////////////////////////////////////
// Receive message LINK_RAMFETCH
void ReceiveLinkRamFetch (wimp_block *pcBlock)
{
  int                         nLinkHandle;
  int                         nLink;
  int                         nSize;
  char                        *pcBuffer;
  int                         nSendLen;

  nLinkHandle = ((int *)(pcBlock->message.data.reserved))[0];
  if (nLinkHandle == gnLinkHandle)
  {
    nLink = ((int *)(pcBlock->message.data.reserved))[1];
    pcBuffer = (char *)(((int *)(pcBlock->message.data.reserved))[2]);
    nSize = ((int *)(pcBlock->message.data.reserved))[3];

    REPORTVAR ("RECV: LinkRamFetch handle = %d", gnLinkHandle);
    REPORTVAR ("      LinkRamFetch link = %d", nLink);
    REPORTVAR ("      LinkRamFetch size = %d", nSize);

    // Transfer data
    if (gnLinkTransLeft < nSize)
    {
      nSendLen = gnLinkTransLeft;
    }
    else
    {
      nSendLen = nSize;
    }

    if (nSendLen > 0)
    {
      xwimp_transfer_block (gnTaskHandle, gpcLinkTransPos,
        pcBlock->message.sender, pcBuffer, nSendLen);
    }

    // Reply with a RamTransmit message
    ((int *)(pcBlock->message.data.reserved))[2] = (int)pcBuffer;
    ((int *)(pcBlock->message.data.reserved))[3] = nSendLen;

    pcBlock->message.size = 36;
    pcBlock->message.your_ref = pcBlock->message.my_ref;
    pcBlock->message.action = message_LINK_RAMTRANSMIT;

    xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
      pcBlock->message.sender);

    REPORTVAR ("SEND: LinkRamTransmit handle = %d", gnLinkHandle);
    REPORTVAR ("      LinkRamTransmit link = %d", nLink);
    REPORTVAR ("      LinkRamTransmit size = %d", nSendLen);

    gpcLinkTransPos += nSendLen;
    gnLinkTransLeft -= nSendLen;

    if (nSendLen < nSize)
    {
      // Everything has been transmitted
      gnLinkTransRef = -1;

      ActOnLinkSent (nLink);
    }
    else
    {
      gnLinkTransRef = pcBlock->message.my_ref;
    }
  }
}

//////////////////////////////////////////////////////////////////
// Receive message LINK_RAMTRANSMIT
void ReceiveLinkRamTransmit (wimp_block *pcBlock)
{
  int                         nLinkHandle;
  int                         nLink;
  int                         nSize;
  char                        *pcBuffer;

  nLinkHandle = ((int *)(pcBlock->message.data.reserved))[0];
  if (nLinkHandle == gnLinkHandle)
  {
    nLink = ((int *)(pcBlock->message.data.reserved))[1];
    nSize = ((int *)(pcBlock->message.data.reserved))[3];
    pcBuffer = (char *)(((int *)(pcBlock->message.data.reserved))[2]);

    REPORTVAR ("RECV: LinkRamTransmit handle = %d", gnLinkHandle);
    REPORTVAR ("      LinkRamTransmit link = %d", nLink);
    REPORTVAR ("      LinkRamTransmit size = %d", nSize);

    if (nSize > 0)
    {
      ActOnLinkSend (nLink, pcBuffer, nSize);
    }

    if (nSize == LINK_RAM_BUFFER)
    {
      // The buffer was full, so we should ask for some more

      // Reply with a RamFetch message
      ((int *)(pcBlock->message.data.reserved))[2] = (int)gpcLinkBuffer;
      ((int *)(pcBlock->message.data.reserved))[3] = LINK_RAM_BUFFER;
      ((int *)(pcBlock->message.data.reserved))[4] = 0;

      pcBlock->message.size = 40;
      pcBlock->message.your_ref = pcBlock->message.my_ref;
      pcBlock->message.action = message_LINK_RAMFETCH;

      xwimp_send_message (wimp_USER_MESSAGE, & pcBlock->message,
        pcBlock->message.sender);

       REPORTVAR ("SEND: LinkRamFetch handle = %d", gnLinkHandle);
      REPORTVAR ("      LinkRamFetch link = %d", nLink);
      REPORTVAR ("      LinkRamFetch size = %d", LINK_RAM_BUFFER);
    }
  }
}

//////////////////////////////////////////////////////////////////
// Send message LINK_OPEN
void LinkOpen (int nLink)
{
  wimp_message                sMessage;

  if (ghLinkTask != NULL)
  {
    ((int *)(sMessage.data.reserved))[0] = gnLinkHandle;
    ((int *)(sMessage.data.reserved))[1] = nLink;

    sMessage.size = 28;
    sMessage.your_ref = 0;
    sMessage.action = message_LINK_OPEN;

    xwimp_send_message (wimp_USER_MESSAGE, & sMessage, ghLinkTask);

    REPORTVAR ("SEND: LinkOpen handle = %d", gnLinkHandle);
    REPORTVAR ("      LinkOpen link = %d", nLink);
  }
}

//////////////////////////////////////////////////////////////////
// Send message LINK_SEND
void LinkSend (int nLink, char * pcData, int nSize)
{
  wimp_message                sMessage;

  if (ghLinkTask != NULL)
  {
    if (nSize <= LINK_SEND_MAX)
    {
      // Can be sent in a single message
      ((int *)(sMessage.data.reserved))[0] = gnLinkHandle;
      ((int *)(sMessage.data.reserved))[1] = nLink;
      ((int *)(sMessage.data.reserved))[2] = nSize;
      memcpy (sMessage.data.reserved + 12, pcData, nSize);

      sMessage.size = WORDALIGN ((32 + nSize));
      sMessage.your_ref = 0;
      sMessage.action = message_LINK_SEND;

      xwimp_send_message (wimp_USER_MESSAGE, & sMessage, ghLinkTask);

      ActOnLinkSent (nLink);

      REPORTVAR ("SEND: LinkSend handle = %d", gnLinkHandle);
      REPORTVAR ("      LinkSend link = %d", nLink);
      REPORTVAR ("      LinkSend size = %d", nSize);
    }
    else
    {
      // Needs to be sent using ram transfers
      ((int *)(sMessage.data.reserved))[0] = gnLinkHandle;
      ((int *)(sMessage.data.reserved))[1] = nLink;
      ((int *)(sMessage.data.reserved))[2] = nSize;

      sMessage.size = 32;
      sMessage.your_ref = 0;
      sMessage.action = message_LINK_DATASAVE;

      xwimp_send_message (wimp_USER_MESSAGE, & sMessage, ghLinkTask);

      REPORTVAR ("SEND: LinkDataSave handle = %d", gnLinkHandle);
      REPORTVAR ("      LinkDataSave link = %d", nLink);
      REPORTVAR ("      LinkDataSave size = %d", nSize);

      gpcLinkTransPos = pcData;
      gnLinkTransLeft = nSize;
    }
  }
}

//////////////////////////////////////////////////////////////////
// Send message LINK_CLOSE
void LinkClose (int nLink)
{
  wimp_message                sMessage;

  if (ghLinkTask != NULL)
  {
    ((int *)(sMessage.data.reserved))[0] = gnLinkHandle;
    ((int *)(sMessage.data.reserved))[1] = nLink;

    sMessage.size = 28;
    sMessage.your_ref = 0;
    sMessage.action = message_LINK_CLOSE;

    xwimp_send_message (wimp_USER_MESSAGE, & sMessage, ghLinkTask);

    REPORTVAR ("SEND: LinkClose handle = %d", gnLinkHandle);
    REPORTVAR ("      LinkClose link = %d", nLink);
  }
}

//////////////////////////////////////////////////////////////////
// Act on initialisation
void ActOnInit (void)
{
  if (gboConcatonate)
  {
    // Open a new window
    CreateEditWindow ();
  }
}

//////////////////////////////////////////////////////////////////
// Act on config request
void ActOnConfig (void)
{
  ConfigSetUpWindow ();
  OpenWindowInitCentre (gwhConf);
}

//////////////////////////////////////////////////////////////////
// Act on config save request
void ActOnConfigSave (MemFile * psMemFile)
{
  char                        szValue[1024];

  sprintf (szValue, "%d", gboConcatonate);
  SaveDetailMem (psMemFile, "Concat", szValue);
  sprintf (szValue, "%d", gboSpecialChars);
  SaveDetailMem (psMemFile, "Special", szValue);
  sprintf (szValue, "%d", gboAllowMouseCaret);
  SaveDetailMem (psMemFile, "Mouse", szValue);
  sprintf (szValue, "%d", gboWarnOnClose);
  SaveDetailMem (psMemFile, "Warn", szValue);
  sprintf (szValue, "%d", gnSendSize);
  SaveDetailMem (psMemFile, "Size", szValue);
}

//////////////////////////////////////////////////////////////////
// Act on config load request
void ActOnConfigLoad (MemFile * psMemFile, int nSection)
{
  char                        szValue[1024];

  FindValueMem (psMemFile, nSection, "Concat", szValue, sizeof (szValue));
  sscanf (szValue, "%d", & gboConcatonate);
  FindValueMem (psMemFile, nSection, "Special", szValue, sizeof (szValue));
  sscanf (szValue, "%d", & gboSpecialChars);
  FindValueMem (psMemFile, nSection, "Mouse", szValue, sizeof (szValue));
  sscanf (szValue, "%d", & gboAllowMouseCaret);
  FindValueMem (psMemFile, nSection, "Warn", szValue, sizeof (szValue));
  sscanf (szValue, "%d", & gboWarnOnClose);
  FindValueMem (psMemFile, nSection, "Size", szValue, sizeof (szValue));
  sscanf (szValue, "%d", & gnSendSize);
}

//////////////////////////////////////////////////////////////////
// Act on the opening of a link
void ActOnLinkOpen (int nLink)
{
  if (nLink == 0)
  {
    if (!gboConcatonate)
    {
      // Open a new window
      CreateEditWindow ();
    }
    gnMiniBufferSize = 0;
    gboBuffersLinked = FALSE;
  }
}

//////////////////////////////////////////////////////////////////
// Act on the closing of a link
void ActOnLinkClose (int nLink)
{
  int                         nStartLine;
  int                         nEndLine;

  if (nLink == 0)
  {
    if (gboConcatonate)
    {
      // Add a newline
      DeleteSection (gsCaret.nEditNum, & gsSelect);
      InsertChar (gsCaret.nEditNum, gsCaret.nPos,
        gsCaret.nLine, '\n');
      ReFormat (gsCaret.nEditNum, gsCaret.nLine, -1,
        & nStartLine, & nEndLine);
      MoveCaretRight (1);
      UpdateEditWindow (gsCaret.nEditNum, nStartLine,
        nEndLine);
      EditChanged (gsCaret.nEditNum);
      ClearSelection ();
    }
  }
}

//////////////////////////////////////////////////////////////////
// Act on the receiving of data on a link
void ActOnLinkSend (int nLink, char * pcData, int nSize)
{
  int                         nStartLine;
  int                         nEndLine;

  if (nLink == 0)
  {
    if (gsCaret.nEditNum == EDIT_INVALID)
    {
      // Open a new window
      CreateEditWindow ();
    }

    if (gboSpecialChars)
    {
      InsertSpecialAtCaret (pcData, nSize);
    }
    else
    {
      if (gboAllowMouseCaret)
      {
        DeleteSection (gsCaret.nEditNum, & gsSelect);
      }

      InsertText (gsCaret.nEditNum, gsCaret.nPos, gsCaret.nLine,
        pcData, nSize);
      ReFormat (gsCaret.nEditNum, gsCaret.nLine, -1,
        & nStartLine, & nEndLine);
      MoveCaretRight (nSize);
      UpdateEditWindow (gsCaret.nEditNum, nStartLine,
        nEndLine);
      EditChanged (gsCaret.nEditNum);
      ClearSelection ();
    }
  }
}

//////////////////////////////////////////////////////////////////
// Act on the data having been sent
void ActOnLinkSent (int nLink)
{
  nLink = nLink;
}

//////////////////////////////////////////////////////////////////
// Set up the configuration window
void ConfigSetUpWindow (void)
{
  char                        szText[1024];

  SetIconSelectionState (!gboConcatonate, gwhConf, 4);
  SetIconSelectionState (gboConcatonate, gwhConf, 5);

  SetIconSelectionState (gboSpecialChars, gwhConf, 8);
  SetIconSelectionState (gboAllowMouseCaret, gwhConf, 9);

  SetIconSelectionState (gboWarnOnClose, gwhConf, 12);

  sprintf (szText, "%d", gnSendSize);
  SetIconText (szText, gwhConf, 14);
}

//////////////////////////////////////////////////////////////////
// Set configuration from the configuration window
void ConfigSetFromWindow (void)
{
  gboConcatonate = GetIconSelectionState (gwhConf, 5);

  gboSpecialChars = GetIconSelectionState (gwhConf, 8);
  gboAllowMouseCaret = GetIconSelectionState (gwhConf, 9);

  gboWarnOnClose = GetIconSelectionState (gwhConf, 12);

  sscanf (GetIconText (gwhConf, 14), "%d", & gnSendSize);
}

//////////////////////////////////////////////////////////////////
// Insert text including special characters
void InsertSpecialAtCaret (char * pcData, int nSize)
{
  int                         nStartLine;
  int                         nEndLine;
  int                         nStartLineMin;
  int                         nEndLineMax;
  int                         nDataPos;
  int                         nDataStart;
  int                         nKey;

  nStartLineMin = gsCaret.nLine;
  nEndLineMax = gsCaret.nLine;

  nDataPos = 0;
  nDataStart = 0;

  while (nDataPos < nSize)
  {
    if (gboBuffersLinked)
    {
      // Add the code into the buffer
      gszMiniBuffer[gnMiniBufferSize] = pcData[nDataPos];
      gnMiniBufferSize++;
      if (gnMiniBufferSize == 4)
      {
        nKey = ((int*)(gszMiniBuffer))[0];
        // Insert the special character
        InsertSpecialKey (nKey, & nStartLine, & nEndLine);
        if (nStartLine < nStartLineMin)
        {
          nStartLineMin = nStartLine;
        }
        if (nEndLine > nEndLineMax)
        {
          nEndLineMax = nEndLine;
        }
        gnMiniBufferSize = 0;
        gboBuffersLinked = FALSE;
        nDataStart = nDataPos + 1;
      }
    }
    else
    {
      // Find the first set of non-special characters
      if (((pcData[nDataPos] < 0x20) || (pcData[nDataPos] == 0x7f))
        && (pcData[nDataPos] != 0x0a))
      {
        if ((nDataPos - nDataStart) > 0)
        {
          // Insert the prior text
          InsertText (gsCaret.nEditNum, gsCaret.nPos, gsCaret.nLine,
            pcData + nDataStart, nDataPos - nDataStart);
          ReFormat (gsCaret.nEditNum, gsCaret.nLine, -1,
            & nStartLine, & nEndLine);
          MoveCaretRight ((nDataPos - nDataStart));
          if (nStartLine < nStartLineMin)
          {
            nStartLineMin = nStartLine;
          }
          if (nEndLine > nEndLineMax)
          {
            nEndLineMax = nEndLine;
          }
          EditChanged (gsCaret.nEditNum);
          ClearSelection ();
        }
        // Deal with the special character
        if (pcData[nDataPos] == 27)
        {
          // Escape code
          gboBuffersLinked = TRUE;
          gnMiniBufferSize = 0;
        }
        else
        {
          InsertSpecialKey ((int)pcData[nDataPos], & nStartLine, & nEndLine);
          if (nStartLine < nStartLineMin)
          {
            nStartLineMin = nStartLine;
          }
          if (nEndLine > nEndLineMax)
          {
            nEndLineMax = nEndLine;
          }
          nDataStart = nDataPos + 1;
        }
      }
    }
    nDataPos++;
  }

  // Add the remaining text
  if ((nDataPos - nDataStart) > 0)
  {
    // Insert the prior text
    InsertText (gsCaret.nEditNum, gsCaret.nPos, gsCaret.nLine,
      pcData + nDataStart, nDataPos - nDataStart);
    ReFormat (gsCaret.nEditNum, gsCaret.nLine, -1,
      & nStartLine, & nEndLine);
    MoveCaretRight ((nDataPos - nDataStart));
    if (nStartLine < nStartLineMin)
    {
      nStartLineMin = nStartLine;
    }
    if (nEndLine > nEndLineMax)
    {
      nEndLineMax = nEndLine;
    }
    EditChanged (gsCaret.nEditNum);
    ClearSelection ();
  }

  UpdateEditWindow (gsCaret.nEditNum, nStartLineMin, nEndLineMax);
}

//////////////////////////////////////////////////////////////////
// Insert a special key
void InsertSpecialKey (int nKey, int * pnStartLine, int * pnEndLine)
{
  bool                        boClearSelect;
  Caret                       sCaretPre;

  *pnStartLine = gsCaret.nLine;
  *pnEndLine = gsCaret.nLine;

  // Check for all of the edit windows
  boClearSelect = FALSE;
  switch (nKey)
  {
    case ('C' - 64):
      CopySelectToClip ();
      boClearSelect = TRUE;
      break;
    case ('X' - 64):
      CopySelectToClip ();
      if (DeleteSection (gsCaret.nEditNum, & gsSelect))
      {
        ReFormat (gsCaret.nEditNum, gsCaret.nLine, -1,
          pnStartLine, pnEndLine);
      }
      break;
    case ('V' - 64):
      PasteClipboard ();
      break;
    case ('Z' - 64):
      boClearSelect = TRUE;
      break;
    case wimp_KEY_TAB:
      DeleteSection (gsCaret.nEditNum, & gsSelect);
      InsertString (gsCaret.nEditNum, gsCaret.nPos,
        gsCaret.nLine, "     ");
      ReFormat (gsCaret.nEditNum, gsCaret.nLine, -1,
        pnStartLine, pnEndLine);
      MoveCaretRight (5);
      boClearSelect = TRUE;
      break;
    case (wimp_KEY_RIGHT + wimp_KEY_CONTROL):
      SearchCaretRight (" \n");
      boClearSelect = TRUE;
      break;
    case (wimp_KEY_LEFT + wimp_KEY_CONTROL):
      SearchCaretLeft (" \n");
      boClearSelect = TRUE;
      break;
    case (wimp_KEY_DOWN + wimp_KEY_CONTROL):
      SearchCaretRight ("\n");
      boClearSelect = TRUE;
      break;
    case (wimp_KEY_UP + wimp_KEY_CONTROL):
      SearchCaretLeft ("\n");
      boClearSelect = TRUE;
      break;
    case (wimp_KEY_RIGHT + wimp_KEY_CONTROL + wimp_KEY_SHIFT):
      sCaretPre = gsCaret;
      SearchCaretRight (" \n");
      ShiftSelection (& sCaretPre, & gsCaret);
      break;
    case (wimp_KEY_LEFT + wimp_KEY_CONTROL + wimp_KEY_SHIFT):
      sCaretPre = gsCaret;
      SearchCaretLeft (" \n");
      ShiftSelection (& sCaretPre, & gsCaret);
      break;
    case (wimp_KEY_DOWN + wimp_KEY_CONTROL + wimp_KEY_SHIFT):
      sCaretPre = gsCaret;
      SearchCaretRight ("\n");
      ShiftSelection (& sCaretPre, & gsCaret);
      break;
    case (wimp_KEY_UP + wimp_KEY_CONTROL + wimp_KEY_SHIFT):
      sCaretPre = gsCaret;
      SearchCaretLeft ("\n");
      ShiftSelection (& sCaretPre, & gsCaret);
      break;
    case (wimp_KEY_LEFT + wimp_KEY_SHIFT):
      sCaretPre = gsCaret;
      MoveCaretLeft(1);
      ShiftSelection (& sCaretPre, & gsCaret);
      break;
    case (wimp_KEY_RIGHT + wimp_KEY_SHIFT):
      sCaretPre = gsCaret;
      MoveCaretRight(1);
      ShiftSelection (& sCaretPre, & gsCaret);
      break;
    case (wimp_KEY_UP + wimp_KEY_SHIFT):
      sCaretPre = gsCaret;
      MoveCaretUp(1);
      ShiftSelection (& sCaretPre, & gsCaret);
      break;
    case (wimp_KEY_DOWN + wimp_KEY_SHIFT):
      sCaretPre = gsCaret;
      MoveCaretDown(1);
      ShiftSelection (& sCaretPre, & gsCaret);
      break;
    case wimp_KEY_LEFT:
      MoveCaretLeft(1);
      boClearSelect = TRUE;
      break;
    case wimp_KEY_RIGHT:
      MoveCaretRight(1);
      boClearSelect = TRUE;
      break;
    case wimp_KEY_UP:
      MoveCaretUp(1);
      boClearSelect = TRUE;
      break;
    case wimp_KEY_DOWN:
      MoveCaretDown(1);
      boClearSelect = TRUE;
      break;

    case wimp_KEY_HOME:
      SetCaretPos (gsCaret.nEditNum, gsCaret.nLine, 0);
        SearchCaretLeft ("\n");
      gsCaret.nCharPref = gsCaret.nChar;
      boClearSelect = TRUE;
      break;

    case wimp_KEY_COPY:
      if (gboIyonixDelete)
      {
        SetCaretPos (gsCaret.nEditNum, gsCaret.nLine,
          gasEdit[gsCaret.nEditNum].anLine[gsCaret.nLine + 1]
          - gasEdit[gsCaret.nEditNum].anLine[gsCaret.nLine] - 1);
        gsCaret.nCharPref = gsCaret.nChar;
          SearchCaretRight ("\n");
      }
      else
      {
        if (!DeleteSection (gsCaret.nEditNum, & gsSelect))
        {
          DeleteChar (gsCaret.nEditNum, gsCaret.nPos,
            gsCaret.nLine);
        }
        ReFormat (gsCaret.nEditNum, gsCaret.nLine, -1,
          pnStartLine, pnEndLine);
      }
      boClearSelect = TRUE;
      break;

    case wimp_KEY_BACKSPACE:
//      case wimp_KEY_DELETE:
      if (!DeleteSection (gsCaret.nEditNum, & gsSelect))
      {
        if (gsCaret.nPos > 0)
        {
          MoveCaretLeft(1);
          DeleteChar (gsCaret.nEditNum, gsCaret.nPos,
            gsCaret.nLine);
        }
      }
      ReFormat (gsCaret.nEditNum, gsCaret.nLine, -1,
        pnStartLine, pnEndLine);
      boClearSelect = TRUE;
      break;

    case wimp_KEY_DELETE:
//      case wimp_KEY_COPY:
      if (!DeleteSection (gsCaret.nEditNum, & gsSelect))
      {
        if (gboIyonixDelete)
        {
          DeleteChar (gsCaret.nEditNum, gsCaret.nPos,
            gsCaret.nLine);
        }
        else
        {
          if (gsCaret.nPos > 0)
          {
            MoveCaretLeft(1);
            DeleteChar (gsCaret.nEditNum, gsCaret.nPos,
              gsCaret.nLine);
          }
        }
      }
      ReFormat (gsCaret.nEditNum, gsCaret.nLine, -1,
        pnStartLine, pnEndLine);
      boClearSelect = TRUE;
      break;



    case wimp_KEY_RETURN:
        DeleteSection (gsCaret.nEditNum, & gsSelect);
        InsertChar (gsCaret.nEditNum, gsCaret.nPos,
          gsCaret.nLine, '\n');
        ReFormat (gsCaret.nEditNum, gsCaret.nLine, -1,
          pnStartLine, pnEndLine);
        MoveCaretRight (1);
        boClearSelect = TRUE;
        break;
    default:
      if ((nKey >= 32) && (nKey < 127))
      {
        DeleteSection (gsCaret.nEditNum, & gsSelect);
        InsertChar (gsCaret.nEditNum, gsCaret.nPos,
          gsCaret.nLine, (char)nKey);
        ReFormat (gsCaret.nEditNum, gsCaret.nLine, -1,
          pnStartLine, pnEndLine);
        MoveCaretRight (1);
        boClearSelect = TRUE;
      }
      else
      {
        xwimp_process_key (nKey);
      }
      break;
  }
  if (boClearSelect
    && (gsSelect.nLineStart != -1)
    && (gsSelect.nEmailNum == gsCaret.nEditNum))
  {
    ClearSelection ();
  }
}

//////////////////////////////////////////////////////////////////
// Send the contents of the edit window
void SendEditData (int nEditNum)
{
  int                         nSuccess;

  // Check in case we're already making a transfer
  if (gnDataSend < 0)
  {
    // Make a copy of the data
    nSuccess = flex_alloc ((flex_ptr)(& gpcDataSend),
      gasEdit[nEditNum].nSize);
    if (nSuccess == 1)
    {
      memcpy (gpcDataSend, gasEdit[nEditNum].szRawEmail,
        gasEdit[nEditNum].nSize);
      ganDataSize = gasEdit[nEditNum].nSize;
      gnDataSend = 0;
      LinkOpen (0);
      SetNullPollActive (NULLPOLL_ACTIVE_FAST);
    }
    else
    {
      ShowWarningTag ("Er3");
    }
  }
  else
  {
    ShowWarningTag ("Er6");
  }
}
